Edit

IABSD.fr/src/usr.bin/systat/engine.c

Branch :

  • Show log

    Commit

  • Author : deraadt
    Date : 2026-02-17 03:26:41
    Hash : d0df355e
    Message : The uvm display abuses the FLD subsystem with a set of empty labels, which results in an extra blank line. Work around this by noticing all the labels are empty and not doing a newline.

  • usr.bin/systat/engine.c
  • /* $OpenBSD: engine.c,v 1.31 2026/02/17 03:26:41 deraadt Exp $	 */
    /*
     * Copyright (c) 2001, 2007 Can Erkin Acar <canacar@openbsd.org>
     *
     * Permission to use, copy, modify, and distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */
    
    
    #include <sys/ioctl.h>
    #include <sys/types.h>
    #include <sys/queue.h>
    
    #include <ctype.h>
    #include <curses.h>
    #include <signal.h>
    #include <stdlib.h>
    #include <string.h>
    #include <term.h>
    #include <unistd.h>
    #include <math.h>
    #include <err.h>
    
    /* XXX These are defined in term.h and conflict with our variable names */
    #ifdef columns
    #undef columns
    #endif
    
    #ifdef lines
    #undef lines
    #endif
    
    #include "engine.h"
    
    #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b))
    
    /* circular linked list of views */
    TAILQ_HEAD(view_list, view_ent) view_head =
    				  TAILQ_HEAD_INITIALIZER(view_head);
    struct view_ent {
    	field_view *view;
    	TAILQ_ENTRY(view_ent) entries;
    };
    
    static struct timespec ts_delay = { 5, 0 };
    static struct itimerval it_delay = { { 0, 0 }, { 5, 0 } };
    
    int dispstart = 0;
    int humanreadable = 0;
    int interactive = 1;
    int averageonly = 0;
    int maxprint = 0;
    int paused = 0;
    int rawmode = 0;
    int rawwidth = DEFAULT_WIDTH;
    int sortdir = 1;
    int columns, lines;
    u_int32_t num_disp = 0;
    int max_disp = -1;
    
    volatile sig_atomic_t gotsig_close = 0;
    volatile sig_atomic_t gotsig_resize = 0;
    volatile sig_atomic_t gotsig_alarm = 0;
    int need_update = 0;
    int need_sort = 0;
    int separate_thousands = 0;
    
    SCREEN *screen;
    
    field_view *curr_view = NULL;
    struct view_ent *curr_view_ent = NULL;
    struct view_manager *curr_mgr = NULL;
    
    int curr_line = 0;
    int home_line = 0;
    
    /* line buffer for raw mode */
    char linebuf[MAX_LINE_BUF];
    int linepos = 0;
    
    /* temp storage for state printing */
    char tmp_buf[MAX_LINE_BUF];
    
    char cmdbuf[MAX_LINE_BUF];
    int cmd_len = -1;
    struct command *curr_cmd = NULL;
    char *curr_message = NULL;
    enum message_mode message_mode = MESSAGE_NONE;
    int message_cont = 1;
    
    void print_cmdline(void);
    
    
    /* screen output functions */
    
    char * tb_ptr = NULL;
    int tb_len = 0;
    
    void
    tb_start(void)
    {
    	tb_ptr = tmp_buf;
    	tb_len = sizeof(tmp_buf);
    	tb_ptr[0] = '\0';
    }
    
    void
    tb_end(void)
    {
    	tb_ptr = NULL;
    	tb_len = 0;
    }
    
    int
    tbprintf(char *format, ...)
    {
    	int len;
    	va_list arg;
    
    	if (tb_ptr == NULL || tb_len <= 0)
    		return 0;
    
    	va_start(arg, format);
    	len = vsnprintf(tb_ptr, tb_len, format, arg);
    	va_end(arg);
    
    	if (len > tb_len)
    		tb_end();
    	else if (len > 0) {
    		tb_ptr += len;
    		tb_len -= len;
    	}
    
    	return len;
    }
    
    int
    tbprintft(char *format, ...)
    {
    	int len;
    	va_list arg;
    	char buf[MAX_LINE_BUF];
    
    	if (tb_ptr == NULL || tb_len <= 0)
    		return 0;
    
    	va_start(arg, format);
    	len = vsnprintf(buf, tb_len, format, arg);
    	va_end(arg);
    
    	if (len > tb_len)
    		tb_end();
    	else if (len > 0) {
    		int d, s;
    		int digits, curdigit;
    
    		if (!separate_thousands) {
    			strlcpy(tb_ptr, buf, tb_len);
    			return len;
    		}
    
    		/* count until we hit a non digit. (e.g. the prefix) */
    		for (digits = 0; digits < len; digits++)
    			if (!isdigit((unsigned char)buf[digits]))
    				break;
    
    		curdigit = digits;
    		d = s = 0;
    		/* insert thousands separators while copying */
    		while (curdigit && d < tb_len) {
    			if (curdigit < digits && curdigit % 3 == 0)
    				tb_ptr[d++] = ',';
    			tb_ptr[d++] = buf[s++];
    			curdigit--;
    		}
    		/* copy the remaining non-digits */
    		while (len > digits && d < tb_len) {
    			tb_ptr[d++] = buf[s++];
    			digits++;
    		}
    		tb_ptr[d] = '\0';
    		tb_ptr += d;
    		tb_len -= d;
    		len = d;
    	}
    	return len;
    }
    
    void
    move_horiz(int offset)
    {
    	if (rawmode) {
    		if (offset <= 0)
    			linepos = 0;
    		else if (offset >= MAX_LINE_BUF)
    			linepos = MAX_LINE_BUF - 1;
    		else
    			linepos = offset;
    	} else {
    		move(curr_line, offset);
    	}
    }
    
    void
    print_str(int len, const char *str)
    {
    	if (len <= 0)
    		return;
    
    	if (rawmode) {
    		int length = MINIMUM(len, MAX_LINE_BUF - linepos);
    		if (length <= 0)
    			return;
    		bcopy(str, &linebuf[linepos], length);
    		linepos += length;
    	} else
    		addnstr(str, len);
    }
    
    void
    clear_linebuf(void)
    {
    	memset(linebuf, ' ', MAX_LINE_BUF);
    }
    
    void
    end_line(void)
    {
    	if (rawmode) {
    		linebuf[rawwidth] = '\0';
    		printf("%s\n", linebuf);
    		clear_linebuf();
    	}
    	curr_line++;
    }
    
    void
    end_page(void)
    {
    	if (rawmode) {
    		linepos = 0;
    		clear_linebuf();
    		fflush(stdout);
    	} else {
    		move(home_line, 0);
    		print_cmdline();
    		refresh();
    	}
    	curr_line = 0;
    }
    
    /* field output functions */
    
    void
    print_fld_str(field_def *fld, const char *str)
    {
    	int len, offset;
    	char *cpos;
    
    	if (str == NULL || fld == NULL)
    		return;
    
    	if (fld->start < 0)
    		return;
    
    	len = strlen(str);
    
    	if (len >= fld->width) {
    		move_horiz(fld->start);
    		print_str(fld->width, str);
    	} else {
    		switch (fld->align) {
    		case FLD_ALIGN_RIGHT:
    			move_horiz(fld->start + (fld->width - len));
    			break;
    		case FLD_ALIGN_CENTER:
    			move_horiz(fld->start + (fld->width - len) / 2);
    			break;
    		case FLD_ALIGN_COLUMN:
    			if ((cpos = strchr(str, ':')) == NULL) {
    				offset = (fld->width - len) / 2;
    			} else {
    				offset = (fld->width / 2) - (cpos - str);
    				if (offset < 0)
    					offset = 0;
    				else if (offset > (fld->width - len))
    					offset = fld->width - len;
    			}
    			move_horiz(fld->start + offset);
    			break;
    		default:
    			move_horiz(fld->start);
    			break;
    		}
    		print_str(len, str);
    	}
    }
    
    void
    print_bar_title(field_def *fld)
    {
    	char buf[16];
    	int len, i, d, tr, tw, val, pos, cur;
    
    	int divs[] = {20, 10, 5, 4, 3, 2, 1, 0};
    
    	if (fld->width < 1)
    		return;
    
    	len = snprintf(buf, sizeof(buf), " %d\\", fld->arg);
    	if (len >= sizeof(buf))
    		return;
    
    	for (i = 0; divs[i]; i++)
    		if (divs[i] * len <= fld->width)
    			break;
    
    	if (divs[i] == 0) {
    		print_fld_str(fld, "*****");
    		return;
    	}
    
    	d = divs[i];
    
    	val = 0;
    	pos = 0;
    	tr = fld->arg % d;
    	tw = fld->width % d;
    
    	tb_start();
    	cur = 0;
    	for(i = 0; i < d; i++) {
    		tw += fld->width;
    		tr += fld->arg;
    
    		while (tr >= d) {
    			val++;
    			tr -= d;
    		}
    		while (tw >= d) {
    			pos++;
    			tw -= d;
    		}
    
    		len = snprintf(buf, sizeof(buf), "%d\\", val);
    		if (len >= sizeof(buf))
    			len = strlen(buf);
    		while (cur < pos - len) {
    			tbprintf(" ");
    			cur++;
    		}
    		tbprintf("%s", buf);
    		cur += len;
    	}
    
    	print_fld_tb(fld);
    }
    
    void
    print_fld_bar(field_def *fld, int value)
    {
    	int i, tw, val;
    
    	if (fld->width < 1)
    		return;
    
    	val = 0;
    	tw = fld->arg / 2;
    
    	tb_start();
    
    	for(i = 0; i < fld->width; i++) {
    		tw += fld->arg;
    
    		while (tw >= fld->width) {
    			val++;
    			tw -= fld->width;
    		}
    		if (val > value)
    			break;
    		tbprintf("#");
    	}
    
    	print_fld_tb(fld);
    }
    
    void
    print_fld_tb(field_def *fld)
    {
    	print_fld_str(fld, tmp_buf);
    	tb_end();
    }
    
    void
    print_title(void)
    {
    	field_def **fp;
    	int nl = 0;
    
    	if (curr_view != NULL && curr_view->view != NULL) {
    		for (fp = curr_view->view; *fp != NULL; fp++) {
    			if ((*fp)->title[0] != '\0')
    				nl++;
    			switch((*fp)->align) {
    			case FLD_ALIGN_LEFT:
    			case FLD_ALIGN_RIGHT:
    			case FLD_ALIGN_CENTER:
    			case FLD_ALIGN_COLUMN:
    				print_fld_str(*fp, (*fp)->title);
    				break;
    			case FLD_ALIGN_BAR:
    				print_bar_title(*fp);
    				break;
    			}
    		}
    		if (nl)
    			end_line();
    	}
    }
    
    /* view related functions */
    void
    hide_field(field_def *fld)
    {
    	if (fld == NULL)
    		return;
    
    	fld->flags |= FLD_FLAG_HIDDEN;
    }
    
    void
    show_field(field_def *fld)
    {
    	if (fld == NULL)
    		return;
    
    	fld->flags &= ~((unsigned int) FLD_FLAG_HIDDEN);
    }
    
    void
    reset_fields(void)
    {
    	field_def **fp;
    	field_def *fld;
    
    	if (curr_view == NULL)
    		return;
    
    	if (curr_view->view == NULL)
    		return;
    
    	for (fp = curr_view->view; *fp != NULL; fp++) {
    		fld = *fp;
    		fld->start = -1;
    		fld->width = fld->norm_width;
    	}
    }
    
    void
    field_setup(void)
    {
    	field_def **fp;
    	field_def *fld;
    	int st, fwid, change;
    	int width = columns;
    
    	reset_fields();
    
    	dispstart = 0;
    	st = 0;
    
    	for (fp = curr_view->view; *fp != NULL; fp++) {
    		fld = *fp;
    		if (fld->flags & FLD_FLAG_HIDDEN)
    			continue;
    
    		if (width <= 1)
    			break;
    
    		if (st != 1)
    			width--;
    
    		fld->start = 1;
    		fwid = fld->width;
    		st++;
    		if (fwid >= width) {
    			fld->width = width;
    			width = 0;
    		} else
    			width -= fwid;
    	}
    
    	while (width > 0) {
    		change = 0;
    		for (fp = curr_view->view; *fp != NULL; fp++) {
    			fld = *fp;
    			if (fld->flags & FLD_FLAG_HIDDEN)
    				continue;
    			if ((fld->width < fld->max_width) &&
    			    (fld->increment <= width)) {
    				int w = fld->width + fld->increment;
    				if (w > fld->max_width)
    					w = fld->max_width;
    				width += fld->width - w;
    				fld->width = w;
    				change = 1;
    			}
    			if (width <= 0) break;
    		}
    		if (change == 0) break;
    	}
    
    	st = 0;
    	for (fp = curr_view->view; *fp != NULL; fp++) {
    		fld = *fp;
    		if (fld->flags & FLD_FLAG_HIDDEN)
    			continue;
    		if (fld->start < 0) break;
    		fld->start = st;
    		st += fld->width + 1;
    	}
    }
    
    void
    set_curr_view(struct view_ent *ve)
    {
    	field_view *v;
    
    	reset_fields();
    
    	if (ve == NULL) {
    		curr_view_ent = NULL;
    		curr_view = NULL;
    		curr_mgr = NULL;
    		return;
    	}
    
    	v = ve->view;
    
    	if ((curr_view != NULL) && (curr_mgr != v->mgr)) {
    		gotsig_alarm = 1;
    		if (v->mgr != NULL && v->mgr->select_fn != NULL)
    			v->mgr->select_fn();
    	}
    
    	curr_view_ent = ve;
    	curr_view = v;
    	curr_mgr = v->mgr;
    	field_setup();
    	need_update = 1;
    }
    
    void
    add_view(field_view *fv)
    {
    	struct view_ent *ent;
    
    	if (fv == NULL)
    		return;
    
    	if (fv->view == NULL || fv->name == NULL || fv->mgr == NULL)
    		return;
    
    	ent = malloc(sizeof(struct view_ent));
    	if (ent == NULL)
    		return;
    
    	ent->view = fv;
    	TAILQ_INSERT_TAIL(&view_head, ent, entries);
    
    	if (curr_view == NULL)
    		set_curr_view(ent);
    }
    
    int
    set_view(const char *opt)
    {
    	struct view_ent *ve, *vm = NULL;
    	field_view *v;
    	int len;
    
    	if (opt == NULL || (len = strlen(opt)) == 0)
    		return 1;
    
    	TAILQ_FOREACH(ve, &view_head, entries) {
    		v = ve->view;
    		if (strncasecmp(opt, v->name, len) == 0) {
    			if (vm)
    				return 1;
    			vm = ve;
    		}
    	}
    
    	if (vm) {
    		set_curr_view(vm);
    		return 0;
    	}
    
    	return 1;
    }
    
    void
    foreach_view(void (*callback)(field_view *))
    {
    	struct view_ent *ve;
    
    	TAILQ_FOREACH(ve, &view_head, entries) {
    		callback(ve->view);
    	}
    }
    
    int
    set_view_hotkey(int ch)
    {
    	struct view_ent *ve;
    	field_view *v;
    	int key = tolower(ch);
    
    	TAILQ_FOREACH(ve, &view_head, entries) {
    		v = ve->view;
    		if (key == v->hotkey) {
    			set_curr_view(ve);
    			return 1;
    		}
    	}
    
    	return 0;
    }
    
    void
    next_view(void)
    {
    	struct view_ent *ve;
    
    	if (TAILQ_EMPTY(&view_head) || curr_view_ent == NULL)
    		return;
    
    	ve = TAILQ_NEXT(curr_view_ent, entries);
    	if (ve == NULL)
    		ve = TAILQ_FIRST(&view_head);
    
    	set_curr_view(ve);
    }
    
    void
    prev_view(void)
    {
    	struct view_ent *ve;
    
    	if (TAILQ_EMPTY(&view_head) || curr_view_ent == NULL)
    		return;
    
    	ve = TAILQ_PREV(curr_view_ent, view_list, entries);
    	if (ve == NULL)
    		ve = TAILQ_LAST(&view_head, view_list);
    
    	set_curr_view(ve);
    }
    
    /* generic field printing */
    
    void
    print_fld_age(field_def *fld, unsigned int age)
    {
    	int len;
    	unsigned int h, m, s;
    
    	if (fld == NULL)
    		return;
    	len = fld->width;
    
    	if (len < 1)
    		return;
    
    	s = age % 60;
    	m = age / 60;
    	h = m / 60;
    	m %= 60;
    
    	tb_start();
    	if (tbprintf("%02u:%02u:%02u", h, m, s) <= len)
    		goto ok;
    
    	tb_start();
    	if (tbprintf("%u", age) <= len)
    		goto ok;
    
    	tb_start();
    	age /= 60;
    	if (tbprintf("%um", age) <= len)
    		goto ok;
    	if (age == 0)
    		goto err;
    
    	tb_start();
    	age /= 60;
    	if (tbprintf("%uh", age) <= len)
    		goto ok;
    	if (age == 0)
    		goto err;
    
    	tb_start();
    	age /= 24;
    	if (tbprintf("%ud", age) <= len)
    		goto ok;
    
    err:
    	print_fld_str(fld, "*");
    	tb_end();
    	return;
    
    ok:
    	print_fld_tb(fld);
    }
    
    void
    print_fld_sdiv(field_def *fld, u_int64_t size, int d)
    {
    	int len;
    	char *mult = "KMGTPE";
    	int i = -1;
    
    	if (fld == NULL)
    		return;
    
    	len = fld->width;
    	if (len < 1)
    		return;
    
    	if (humanreadable) {
    		while (size >= 10000 && sizeof(mult) >= i + 1) {
    			i++;
    			size /= d;
    		}
    		tb_start();
    		if (tbprintft("%llu%.1s", size, i == -1 ? "" : mult + i) <= len)
    			goto ok;
    		goto err;
    	}
    	do {
    		tb_start();
    		if (tbprintft("%llu%.1s", size, i == -1 ? "" : mult + i) <= len)
    			goto ok;
    		i++;
    		size /= d;
    	} while (size != 0 && sizeof(mult) >= i);
    err:
    	tb_start();
    	print_fld_str(fld, "*");
    	tb_end();
    	return;
    
    ok:
    	print_fld_tb(fld);
    }
    
    void
    print_fld_size(field_def *fld, u_int64_t size)
    {
    	print_fld_sdiv(fld, size, 1024);
    }
    
    void
    print_fld_ssdiv(field_def *fld, int64_t size, int d)
    {
    	int len;
    
    	if (fld == NULL)
    		return;
    
    	len = fld->width;
    	if (len < 1)
    		return;
    
    	tb_start();
    	if (tbprintft("%lld", size) <= len)
    		goto ok;
    
    	tb_start();
    	size /= d;
    	if (tbprintft("%lldK", size) <= len)
    		goto ok;
    	if (size == 0)
    		goto err;
    
    	tb_start();
    	size /= d;
    	if (tbprintft("%lldM", size) <= len)
    		goto ok;
    	if (size == 0)
    		goto err;
    
    	tb_start();
    	size /= d;
    	if (tbprintft("%lldG", size) <= len)
    		goto ok;
    	if (size == 0)
    		goto err;
    
    	tb_start();
    	size /= d;
    	if (tbprintft("%lldT", size) <= len)
    		goto ok;
    
    err:
    	print_fld_str(fld, "*");
    	tb_end();
    	return;
    
    ok:
    	print_fld_tb(fld);
    }
    
    void
    print_fld_ssize(field_def *fld, int64_t size)
    {
    	print_fld_ssdiv(fld, size, 1024);
    }
    
    void
    print_fld_rate(field_def *fld, double rate)
    {
    	if (rate < 0) {
    		print_fld_str(fld, "*");
    	} else {
    		print_fld_size(fld, rate);
    	}
    }
    
    void
    print_fld_bw(field_def *fld, double bw)
    {
    	if (bw < 0) {
    		print_fld_str(fld, "*");
    	} else {
    		print_fld_sdiv(fld, bw, 1000);
    	}
    }
    
    void
    print_fld_uint(field_def *fld, unsigned int size)
    {
    	int len;
    
    	if (fld == NULL)
    		return;
    
    	len = fld->width;
    	if (len < 1)
    		return;
    
    	tb_start();
    	if (tbprintft("%u", size) > len)
    		print_fld_str(fld, "*");
    	else
    		print_fld_tb(fld);
    	tb_end();
    }
    
    void
    print_fld_float(field_def *fld, double f, int prec)
    {
    	int len;
    
    	if (fld == NULL)
    		return;
    
    	len = fld->width;
    	if (len < 1)
    		return;
    
    	tb_start();
    	if (tbprintf("%*.*f", len, prec, f) > len)
    		print_fld_str(fld, "*");
    	else
    		print_fld_tb(fld);
    	tb_end();
    }
    
    
    /* ordering */
    
    int
    foreach_order(void (*callback)(order_type *))
    {
    	order_type *o;
    
    	if (curr_view == NULL || curr_view->mgr == NULL ||
    	    curr_view->mgr->order_list == NULL)
    		return -1;
    	o = curr_view->mgr->order_list;
    	do {
    		callback(o++);
    	} while (o->name != NULL);
    	return 0;
    }
    
    void
    set_order(const char *opt)
    {
    	order_type *o;
    
    	if (curr_view == NULL || curr_view->mgr == NULL)
    		return;
    
    	curr_view->mgr->order_curr = curr_view->mgr->order_list;
    
    	if (opt == NULL)
    		return;
    
    	o = curr_view->mgr->order_list;
    
    	if (o == NULL)
    		return;
    
    	for (;o->name != NULL; o++) {
    		if (strcasecmp(opt, o->match) == 0) {
    			curr_view->mgr->order_curr = o;
    			return;
    		}
    	}
    }
    
    int
    set_order_hotkey(int ch)
    {
    	order_type *o;
    	int key = ch;
    
    	if (curr_view == NULL || curr_view->mgr == NULL)
    		return 0;
    
    	o = curr_view->mgr->order_list;
    
    	if (o == NULL)
    		return 0;
    
    	for (;o->name != NULL; o++) {
    		if (key == o->hotkey) {
    			if (curr_view->mgr->order_curr == o) {
    				sortdir *= -1;
    			} else {
    				curr_view->mgr->order_curr = o;
    			}
    			return 1;
    		}
    	}
    
    	return 0;
    }
    
    void
    next_order(void)
    {
    	order_type *o, *oc;
    
    	if (curr_view->mgr->order_list == NULL)
    		return;
    
    	oc = curr_view->mgr->order_curr;
    
    	for (o = curr_view->mgr->order_list; o->name != NULL; o++) {
    		if (oc == o) {
    			o++;
    			if (o->name == NULL)
    				break;
    			curr_view->mgr->order_curr = o;
    			return;
    		}
    	}
    
    	curr_view->mgr->order_curr = curr_view->mgr->order_list;
    }
    
    
    /* main program functions */
    
    int
    read_view(void)
    {
    	if (curr_mgr == NULL)
    		return (0);
    
    	if (paused)
    		return (0);
    
    	if (curr_mgr->read_fn != NULL)
    		return (curr_mgr->read_fn());
    
    	return (0);
    }
    
    
    int
    disp_update(void)
    {
    	int li;
    
    	if (maxprint < 0)
    		dispstart = 0;
    	else if (dispstart + maxprint > num_disp)
    		dispstart = num_disp - maxprint;
    
    	if (dispstart < 0)
    		dispstart = 0;
    
    	if (curr_view == NULL)
    		return 0;
    
    	if (curr_mgr != NULL) {
    		curr_line = 0;
    
    		if (curr_mgr->header_fn != NULL) {
    			li = curr_mgr->header_fn();
    			if (li < 0)
    				return (1);
    			curr_line = ++li;
    			home_line = li + maxprint + 1;
    		}
    
    		print_title();
    
    		if (curr_mgr->print_fn != NULL)
    			curr_mgr->print_fn();
    	}
    
    	return (0);
    }
    
    void
    sort_view(void)
    {
    	if (curr_mgr != NULL)
    		if (curr_mgr->sort_fn != NULL)
    			curr_mgr->sort_fn();
    }
    
    void
    sig_close(int sig)
    {
    	gotsig_close = 1;
    }
    
    void
    sig_resize(int sig)
    {
    	gotsig_resize = 1;
    }
    
    void
    sig_alarm(int sig)
    {
    	gotsig_alarm = 1;
    }
    
    void
    setup_term(int dmax)
    {
    	max_disp = dmax;
    	maxprint = dmax;
    
    	if (rawmode) {
    		columns = rawwidth;
    		lines = DEFAULT_HEIGHT;
    		clear_linebuf();
    	} else {
    		if (dmax < 0)
    			dmax = 0;
    
    		screen = newterm(NULL, stdout, stdin);
    		if (screen == NULL) {
    			rawmode = 1;
    			interactive = 0;
    			setup_term(dmax);
    			return;
    		}
    		columns = COLS;
    		lines = LINES;
    
    		if (maxprint > lines - HEADER_LINES)
    			maxprint = lines - HEADER_LINES;
    
    		nonl();
    		keypad(stdscr, TRUE);
    		intrflush(stdscr, FALSE);
    
    		halfdelay(10);
    		noecho();
    	}
    
    	if (dmax == 0)
    		maxprint = lines - HEADER_LINES;
    
    	field_setup();
    }
    
    void
    do_resize_term(void)
    {
    	struct winsize ws;
    
    	if (rawmode)
    		return;
    
    	if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
    		return;
    
    	resizeterm(ws.ws_row, ws.ws_col);
    
    	columns = COLS;
    	lines = LINES;
    
    	maxprint = max_disp;
    
    	if (maxprint == 0 || maxprint > lines - HEADER_LINES)
    		maxprint = lines - HEADER_LINES;
    
    	clear();
    
    	field_setup();
    }
    
    struct command *
    command_set(struct command *cmd, const char *init)
    {
    	struct command *prev = curr_cmd;
    
    	if (cmd) {
    		if (init) {
    			cmd_len = strlcpy(cmdbuf, init, sizeof(cmdbuf));
    			if (cmd_len >= sizeof(cmdbuf)) {
    				cmdbuf[0] = '\0';
    				cmd_len = 0;
    			}
    		} else {
    			cmd_len = 0;
    			cmdbuf[0] = 0;
    		}
    	}
    	message_set(NULL);
    	curr_cmd = cmd;
    	need_update = 1;
    	return prev;
    }
    
    void
    message_toggle(enum message_mode mode)
    {
    	message_mode = message_mode != mode ? mode : MESSAGE_NONE;
    	need_update = 1;
    	message_cont = 1;
    }
    
    const char *
    message_set(const char *msg)
    {
    	free(curr_message);
    
    	if (msg) {
    		curr_message = strdup(msg);
    		message_cont = 0;
    	} else {
    		curr_message = NULL;
    		message_cont = 1;
    	}
    	return NULL;
    }
    
    void
    print_cmdline(void)
    {
    	if (curr_cmd) {
    		attron(A_STANDOUT);
    		mvprintw(home_line, 0, "%s: ", curr_cmd->prompt);
    		attroff(A_STANDOUT);
    		printw("%s", cmdbuf);
    	} else if (curr_message) {
    		mvprintw(home_line, 0, "> %s", curr_message);
    	}
    	clrtoeol();
    }
    
    
    void
    cmd_keyboard(int ch)
    {
    	if (curr_cmd == NULL)
    		return;
    
    	if (ch > 0 && isprint(ch)) {
    		if (cmd_len < sizeof(cmdbuf) - 1) {
    			cmdbuf[cmd_len++] = ch;
    			cmdbuf[cmd_len] = 0;
    		} else
    			beep();
    	}
    
    	switch (ch) {
    	case KEY_ENTER:
    	case 0x0a:
    	case 0x0d:
    	{
    		struct command * c = command_set(NULL, NULL);
    		c->exec(cmdbuf);
    		break;
    	}
    	case KEY_BACKSPACE:
    	case KEY_DC:
    	case CTRL_H:
    		if (cmd_len > 0) {
    			cmdbuf[--cmd_len] = 0;
    		} else
    			beep();
    		break;
    	case 0x1b:
    	case CTRL_G:
    		if (cmd_len > 0) {
    			cmdbuf[0] = '\0';
    			cmd_len = 0;
    		} else
    			command_set(NULL, NULL);
    		break;
    	default:
    		break;
    	}
    }
    
    void
    keyboard(void)
    {
    	int ch;
    
    	ch = getch();
    
    	if (curr_cmd) {
    		cmd_keyboard(ch);
    		print_cmdline();
    		return;
    	}
    
    	if (curr_mgr != NULL)
    		if (curr_mgr->key_fn != NULL)
    			if (curr_mgr->key_fn(ch))
    				return;
    
    	if (curr_message != NULL) {
    		if (ch > 0) {
    			message_set(NULL);
    			need_update = 1;
    		}
    	}
    
    	switch (ch) {
    	case ' ':
    		gotsig_alarm = 1;
    		break;
    	case 'o':
    		next_order();
    		need_sort = 1;
    		break;
    	case 'p':
    		paused = !paused;
    		gotsig_alarm = 1;
    		break;
    	case 'q':
    		gotsig_close = 1;
    		break;
    	case 'r':
    		sortdir *= -1;
    		need_sort = 1;
    		break;
    	case 'v':
    		/* FALLTHROUGH */
    	case KEY_RIGHT:
    		/* FALLTHROUGH */
    	case CTRL_F:
    		next_view();
    		break;
    	case KEY_LEFT:
    		/* FALLTHROUGH */
    	case CTRL_B:
    		prev_view();
    		break;
    	case KEY_DOWN:
    		/* FALLTHROUGH */
    	case CTRL_N:
    		dispstart++;
    		need_update = 1;
    		break;
    	case KEY_UP:
    		/* FALLTHROUGH */
    	case CTRL_P:
    		dispstart--;
    		need_update = 1;
    		break;
    	case KEY_NPAGE:
    		/* FALLTHROUGH */
    	case CTRL_V:
    		dispstart += maxprint;
    		need_update = 1;
    		break;
    	case KEY_PPAGE:
    		/* FALLTHROUGH */
    	case META_V:
    		dispstart -= maxprint;
    		need_update = 1;
    		break;
    	case KEY_HOME:
    		/* FALLTHROUGH */
    	case CTRL_A:
    		dispstart = 0;
    		need_update = 1;
    		break;
    	case KEY_END:
    		/* FALLTHROUGH */
    	case CTRL_E:
    		dispstart = num_disp;
    		need_update = 1;
    		break;
    	case CTRL_L:
    		clear();
    		need_update = 1;
    		break;
    	default:
    		break;
    	}
    
    	if (set_order_hotkey(ch))
    		need_sort = 1;
    	else
    		set_view_hotkey(ch);
    }
    
    void
    engine_initialize(void)
    {
    	signal(SIGTERM, sig_close);
    	signal(SIGINT, sig_close);
    	signal(SIGQUIT, sig_close);
    	signal(SIGWINCH, sig_resize);
    	signal(SIGALRM, sig_alarm);
    }
    
    void
    engine_loop(int countmax)
    {
    	int count = 0;
    
    	for (;;) {
    		if (gotsig_alarm) {
    			read_view();
    			need_sort = 1;
    			gotsig_alarm = 0;
    			setitimer(ITIMER_REAL, &it_delay, NULL);
    		}
    
    		if (need_sort) {
    			sort_view();
    			need_sort = 0;
    			need_update = 1;
    
    			/* XXX if sort took too long */
    			if (gotsig_alarm) {
    				gotsig_alarm = 0;
    				setitimer(ITIMER_REAL, &it_delay, NULL);
    			}
    		}
    
    		if (need_update) {
    			erase();
    			if (!averageonly ||
    			    (averageonly && count == countmax - 1))
    				disp_update();
    			if (message_cont) {
    				switch (message_mode) {
    				case MESSAGE_NONE:
    					message_set(NULL);
    					break;
    				case MESSAGE_HELP:
    					show_help();
    					break;
    				case MESSAGE_VIEW:
    					show_view();
    					break;
    				case MESSAGE_ORDER:
    					show_order();
    					break;
    				}
    			}
    			end_page();
    			need_update = 0;
    			if (countmax && ++count >= countmax)
    				break;
    		}
    
    		if (gotsig_close)
    			break;
    		if (gotsig_resize) {
    			do_resize_term();
    			gotsig_resize = 0;
    			need_update = 1;
    		}
    
    		if (interactive && need_update == 0)
    			keyboard();
    		else if (interactive == 0)
    			nanosleep(&ts_delay, NULL);
    	}
    
    	if (rawmode == 0)
    		endwin();
    }
    
    int
    check_termcap(void)
    {
    	char *term_name;
    	int status;
    	static struct termios screen_settings;
    
    	if (!interactive)
    		/* pretend we have a dumb terminal */
    		return(1);
    
    	/* get the terminal name */
    	term_name = getenv("TERM");
    	if (term_name == NULL)
    		return(1);
    
    	/* now get the termcap entry */
    	if ((status = tgetent(NULL, term_name)) != 1) {
    		if (status == -1)
    			warnx("can't open termcap file");
    		else
    			warnx("no termcap entry for a `%s' terminal",
    			    term_name);
    
    		/* pretend it's dumb and proceed */
    		return(1);
    	}
    
    	/* "hardcopy" immediately indicates a very stupid terminal */
    	if (tgetflag("hc"))
    		return(1);
    
            /* get necessary capabilities */
            if (tgetstr("cl", NULL) == NULL || tgetstr("cm", NULL) == NULL)
    		return(1);
    
    	/* if stdout is not a terminal, pretend we are a dumb terminal */
    	if (tcgetattr(STDOUT_FILENO, &screen_settings) == -1)
    		return(1);
    
    	return(0);
    }
    
    void
    refresh_delay(double delay)
    {
    	double secs, frac;
    
    	frac = modf(delay, &secs);
    	ts_delay.tv_sec = secs;
    	ts_delay.tv_nsec = frac * 1000000000.0;
    	if (!timespecisset(&ts_delay))
    		ts_delay.tv_nsec = 1000000000;
    	TIMESPEC_TO_TIMEVAL(&it_delay.it_value, &ts_delay);
    }