root/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);
}