Files
nvidia-installer/ncurses-ui.c
Aaron Plattner 251ec51c59 545.23.06
2023-10-17 10:03:12 -07:00

1995 lines
52 KiB
C

/*
* nvidia-installer: A tool for installing NVIDIA software packages on
* Unix and Linux systems.
*
* Copyright (C) 2003 NVIDIA Corporation
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses>.
*
*
* ncurses_ui.c - implementation of the nvidia-installer ui using ncurses.
*/
#include <ncurses.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include "nvidia-installer.h"
#include "nvidia-installer-ui.h"
/* structures */
/*
* RegionStruct - a region is sort of like an ncurses window, but
* implemented completely in this source file (ie: doesn't use ncurses
* windows). I found enough bugs with ncurses windows that I felt it
* better to avoid their use altogether.
*/
typedef struct {
int x, y, w, h; /* position and dimensions relative to stdscr */
int attr; /* attributes (to be passed to setattr()) */
char *line; /* a string filled with spaces; its length is the
width of the region. This is just a
convenience for use when clearing the region */
} RegionStruct;
/*
* PagerStruct - Pager implements the functionality of `less`
*/
typedef struct {
TextRows *t; /* rows of text to be displayed */
RegionStruct *region; /* region in which the text will be displayed */
const char *label; /* label for the left side of the footer */
int cur; /* current position in the pager */
int page; /* height of a page (for use with pgup/pgdn) */
} PagerStruct;
/*
* DataStruct - private data structure that gets plugged into
* Options->ui.priv.
*/
typedef struct {
RegionStruct *header; /* header region */
RegionStruct *footer; /* footer region */
RegionStruct *message; /* message region */
FormatTextRows format_text_rows; /* XXX function pointer from installer */
bool use_color; /* should the ncurses ui use color? */
int width; /* cached copy of the terminal dimensions */
int height;
char *title; /* cached strings for the header and footer */
char *footer_left;
char *footer_right;
char *progress_title; /* cached string for the title of the
progress messages */
} DataStruct;
/* constants */
/* default strings for the header and footer */
#define NV_NCURSES_DEFAULT_TITLE "NVIDIA Software Installer for Unix/Linux"
#define NV_NCURSES_DEFAULT_FOOTER_LEFT NV_NCURSES_DEFAULT_TITLE
#define NV_NCURSES_DEFAULT_FOOTER_RIGHT "www.nvidia.com"
/* indices for color pairs */
#define NV_NCURSES_HEADER_COLOR_IDX 1
#define NV_NCURSES_MESSAGE_COLOR_IDX 2
#define NV_NCURSES_BUTTON_COLOR_IDX 3
#define NV_NCURSES_INPUT_COLOR_IDX 4
#define NV_NCURSES_HEADER_COLOR (COLOR_PAIR(NV_NCURSES_HEADER_COLOR_IDX))
#define NV_NCURSES_FOOTER_COLOR A_REVERSE
#define NV_NCURSES_MESSAGE_COLOR (COLOR_PAIR(NV_NCURSES_MESSAGE_COLOR_IDX))
#define NV_NCURSES_BUTTON_COLOR (COLOR_PAIR(NV_NCURSES_BUTTON_COLOR_IDX))
#define NV_NCURSES_INPUT_COLOR (COLOR_PAIR(NV_NCURSES_INPUT_COLOR_IDX))
#define NV_NCURSES_HEADER_NO_COLOR A_REVERSE
#define NV_NCURSES_FOOTER_NO_COLOR A_REVERSE
#define NV_NCURSES_MESSAGE_NO_COLOR A_NORMAL
/* use when animating button presses */
#define NV_NCURSES_BUTTON_PRESS_TIME 125000
#define NV_NCURSES_TAB 9
#define NV_NCURSES_ENTER 10
#define NV_NCURSES_BACKSPACE 8
#define NV_NCURSES_CTRL(x) ((x) & 0x1f)
/*
* somewhat arbitrary minimum values: if the current window size is
* smaller than this, don't attempt to use the ncurses ui.
*/
#define NV_NCURSES_MIN_WIDTH 40
#define NV_NCURSES_MIN_HEIGHT 10
#define NV_NCURSES_HLINE '_'
/* prototypes for ui entry points */
static int nv_ncurses_detect (Options*);
static int nv_ncurses_init (Options*,
FormatTextRows format_text_rows);
static void nv_ncurses_set_title (Options*, const char*);
static char *nv_ncurses_get_input (Options*, const char*,
const char*);
static void nv_ncurses_message (Options*, const int level,
const char*);
static void nv_ncurses_command_output (Options*, const char*);
static int nv_ncurses_approve_command_list(Options*, CommandList*,
const char*);
static int nv_ncurses_yes_no (Options*, const int, const char*);
static int nv_ncurses_multiple_choice (Options *, const char*,
const char * const *, int, int);
static void nv_ncurses_status_begin (Options*, const char*,
const char*);
static void nv_ncurses_status_update (Options*, const float,
const char*);
static void nv_ncurses_update_indeterminate(Options*, const char*);
static void nv_ncurses_status_end (Options*, const char*);
static void nv_ncurses_close (Options*);
/* helper functions for manipulating the header and footer */
static void nv_ncurses_set_header(DataStruct *, const char *);
static void nv_ncurses_set_footer(DataStruct *, const char *, const char *);
/* helper functions for manipulating RegionStructs */
static RegionStruct *nv_ncurses_create_region(DataStruct *, int, int,
int, int, int, int);
static void nv_ncurses_clear_region(RegionStruct *);
static void nv_ncurses_destroy_region(RegionStruct *);
/* helper functions for drawing buttons */
static void nv_ncurses_draw_button(DataStruct *, RegionStruct *, int, int,
int, int, const char *, bool, bool);
static void nv_ncurses_erase_button(RegionStruct *, int, int, int, int);
static void draw_buttons(DataStruct *d, const char * const *buttons,
int num_buttons, int button, int button_w,
const int *buttons_x, int button_y);
/* helper functions for drawing regions and stuff */
static void nv_ncurses_do_message_region(DataStruct *, const char *,
const char *, int, int);
static void nv_ncurses_do_progress_bar_region(DataStruct *);
static void nv_ncurses_do_progress_bar_message(DataStruct *, const char *,
int, int);
/* pager functions */
static PagerStruct *nv_ncurses_create_pager(DataStruct *, int, int, int, int,
TextRows *, const char *, int);
static void nv_ncurses_pager_update(DataStruct *, PagerStruct *);
static void nv_ncurses_pager_handle_events(DataStruct *, PagerStruct *, int);
static void nv_ncurses_destroy_pager(PagerStruct *);
static int nv_ncurses_paged_prompt(Options *, const char*, const char*,
const char*, const char * const *, int, int);
/* progress bar helper functions */
static int choose_char(int i, int p[4], char v[4], char def);
static void init_percentage_string(char v[4], int n);
static void init_position(int p[4], int w);
/* misc helper functions */
static char *nv_ncurses_create_command_list_text(DataStruct *, CommandList *);
static void nv_ncurses_free_text_rows(TextRows *);
static int nv_ncurses_format_print(DataStruct *, RegionStruct *,
int, int, int, int, TextRows *t);
static int nv_ncurses_check_resize(DataStruct *, bool);
/* dispatch table that gets dlsym()'ed by ui_init() */
InstallerUI ui_dispatch_table = {
nv_ncurses_detect,
nv_ncurses_init,
nv_ncurses_set_title,
nv_ncurses_get_input,
nv_ncurses_message,
nv_ncurses_command_output,
nv_ncurses_approve_command_list,
nv_ncurses_yes_no,
nv_ncurses_multiple_choice,
nv_ncurses_paged_prompt,
nv_ncurses_status_begin,
nv_ncurses_status_update,
nv_ncurses_status_end,
nv_ncurses_update_indeterminate,
nv_ncurses_close
};
/* internal variables */
/* Save the return value of initscr() here instead of using stdscr directly.
* This avoids problems if libncurses.so doesn't expose stdscr, as was the
* case in http://bugzilla.suse.com/show_bug.cgi?id=1132282 */
static WINDOW *nv_stdscr;
/*
* nv_ncurses_detect() - initialize ncurses; return FALSE if
* initialization fails
*/
static int nv_ncurses_detect(Options *op)
{
int x, y;
if (!(nv_stdscr = initscr())) return FALSE;
/*
* query the current size of the window, and don't try to use the
* ncurses ui if it's too small.
*/
getmaxyx(nv_stdscr, y, x);
if ((x < NV_NCURSES_MIN_WIDTH) || (y < NV_NCURSES_MIN_HEIGHT)) {
endwin();
return FALSE;
}
return TRUE;
} /* nv_ncurses_detect() */
/*
* nv_ncurses_init() - initialize the ncurses interface.
*/
static int nv_ncurses_init(Options *op, FormatTextRows format_text_rows)
{
DataStruct *d;
d = (DataStruct *) malloc(sizeof(DataStruct));
memset(d, 0, sizeof(DataStruct));
d->format_text_rows = format_text_rows;
/* initialize color */
d->use_color = !op->no_ncurses_color;
if (d->use_color) {
if (!has_colors()) {
d->use_color = FALSE;
}
}
if (d->use_color) {
if (start_color() == ERR) {
d->use_color = FALSE;
} else {
/* foreground background */
init_pair(NV_NCURSES_HEADER_COLOR_IDX, COLOR_BLACK, COLOR_GREEN);
init_pair(NV_NCURSES_MESSAGE_COLOR_IDX, COLOR_WHITE, COLOR_BLUE);
init_pair(NV_NCURSES_BUTTON_COLOR_IDX, COLOR_WHITE, COLOR_RED);
init_pair(NV_NCURSES_INPUT_COLOR_IDX, COLOR_GREEN, COLOR_BLACK);
}
}
wclear(nv_stdscr); /* clear the screen */
noecho(); /* don't echo input to the screen */
cbreak(); /* disable line buffering and control characters */
curs_set(0); /* make the cursor invisible */
keypad(nv_stdscr, TRUE); /* enable keypad, function keys, arrow keys, etc */
getmaxyx(nv_stdscr, d->height, d->width); /* get current window dimensions */
/* create the regions */
d->header = nv_ncurses_create_region(d, 1, 0, d->width - 2, 1,
NV_NCURSES_HEADER_COLOR,
NV_NCURSES_HEADER_NO_COLOR);
d->footer = nv_ncurses_create_region(d, 1, d->height - 2, d->width - 2, 1,
NV_NCURSES_FOOTER_COLOR,
NV_NCURSES_FOOTER_NO_COLOR);
/* plug the DataStruct struct into the ui.priv pointer */
op->ui.priv = (void *) d;
/* set the initial strings in the header and footer */
nv_ncurses_set_header(d, NV_NCURSES_DEFAULT_TITLE);
nv_ncurses_set_footer(d, NV_NCURSES_DEFAULT_FOOTER_LEFT,
NV_NCURSES_DEFAULT_FOOTER_RIGHT);
wrefresh(nv_stdscr);
return TRUE;
} /* nv_ncurses_init() */
/*
* nv_ncurses_set_title() - update the string in the header region
*/
static void nv_ncurses_set_title(Options *op, const char *title)
{
DataStruct *d = (DataStruct *) op->ui.priv;
nv_ncurses_set_header(d, title);
wrefresh(nv_stdscr);
} /* nv_ncurses_set_title() */
/*
* nv_ncurses_get_input - prompt for user input with the given msg;
* returns the user inputted string.
*/
#define MIN_INPUT_LEN 32
#define MAX_BUF_LEN 1024
#define BUF_CHAR(c) ((c) ? (c) : ' ')
static char *nv_ncurses_get_input(Options *op,
const char *def, const char *msg)
{
DataStruct *d = (DataStruct *) op->ui.priv;
int msg_len, width, input_len, buf_len, def_len;
int input_x, input_y, i, w, h, x, y, c, lines, ch, color, redraw;
char *tmp, buf[MAX_BUF_LEN];
TextRows *t;
if (!msg) return NULL;
nv_ncurses_check_resize(d, FALSE);
color = d->use_color ? NV_NCURSES_INPUT_COLOR : A_REVERSE;
/* concatenate ": " to the end of the message */
msg_len = strlen(msg) + 2;
tmp = (char *) malloc(msg_len + 1);
snprintf(tmp, msg_len, "%s: ", msg);
/* copy the default response into the buffer */
memset(buf, 0, MAX_BUF_LEN);
if (def) strncpy(buf, def, MAX_BUF_LEN - 1);
buf[MAX_BUF_LEN - 1] = '\0';
draw_get_input:
/* free any existing message region */
if (d->message) {
nv_ncurses_destroy_region(d->message);
d->message = NULL;
}
/*
* compute the size of the input box: the input box is twice the
* length of the default string, clamped to MIN_INPUT_LEN
*/
def_len = def ? strlen(def) : 0;
input_len = NV_MAX(def_len * 2, MIN_INPUT_LEN);
width = d->width - 4;
/* convert the message to text rows */
t = d->format_text_rows(NULL, tmp, width, TRUE);
/*
* if the message and input box will fit on one line, do that;
* otherwise, place them on separate lines
*/
if ((msg_len + input_len + 1) < width) {
input_x = 1 + msg_len;
lines = 1;
} else {
input_x = 2;
lines = t->n + 1;
}
input_y = lines;
/*
* compute the width, height, and starting position of the message
* region
*/
w = d->width - 2;
h = lines + 2;
x = 1;
y = ((d->height - (3 + h)) / 3) + 1;
/* create the message region */
d->message = nv_ncurses_create_region(d, x, y, w, h,
NV_NCURSES_MESSAGE_COLOR,
NV_NCURSES_MESSAGE_NO_COLOR);
nv_ncurses_format_print(d, d->message, 1, 1, d->message->w - 2, lines, t);
/* free the text rows */
nv_ncurses_free_text_rows(t);
/* clamp the input box to the width of the region */
input_len = NV_MIN(input_len, width - 2);
curs_set(1); /* make the cursor visible */
c = buf_len = strlen(buf);
redraw = TRUE; /* force a redraw the first time through */
/* offset input_x and input_y by the region offset */
input_x += d->message->x;
input_y += d->message->y;
do {
x = NV_MAX(c - (input_len - 1), 0);
/* redraw the input box */
if (redraw) {
for (i = 0; i < input_len; i++) {
mvwaddch(nv_stdscr, input_y, input_x + i,
BUF_CHAR(buf[i + x]) | color);
}
/* if we're scrolling, display an arrow */
if (x > 0) {
mvwaddch(nv_stdscr, input_y, input_x - 1,
'<' | d->message->attr);
} else {
mvwaddch(nv_stdscr, input_y, input_x - 1,
' ' | d->message->attr);
}
if (buf_len > (input_len - 1 + x)) {
mvwaddch(nv_stdscr, input_y, input_x + input_len,
'>' | d->message->attr);
} else {
mvwaddch(nv_stdscr, input_y, input_x + input_len,
' ' | d->message->attr);
}
redraw = FALSE;
}
/* position the cursor */
wmove(nv_stdscr, input_y, input_x - x + c);
wrefresh(nv_stdscr);
/* wait for input */
if (nv_ncurses_check_resize(d, FALSE)) goto draw_get_input;
ch = wgetch(nv_stdscr);
switch (ch) {
case NV_NCURSES_BACKSPACE:
case KEY_BACKSPACE:
/*
* If there is a character to be deleted, move everything
* after the character forward, and decrement c and len.
*/
if (c <= 0) break;
for (i = c; i <= buf_len; i++) buf[i-1] = buf[i];
c--;
buf_len--;
redraw = TRUE;
break;
case KEY_DC:
/*
* If there is a character to be deleted, move everything
* after the character forward, and decrement len.
*/
if (c == buf_len) break;
for (i = c; i < buf_len; i++) buf[i] = buf[i+1];
buf_len--;
redraw = TRUE;
break;
case KEY_LEFT:
if (c > 0) {
c--;
redraw = TRUE;
}
break;
case KEY_RIGHT:
if (c < buf_len) {
c++;
redraw = TRUE;
}
break;
case NV_NCURSES_CTRL('L'):
nv_ncurses_check_resize(d, TRUE);
goto draw_get_input;
break;
}
/*
* If we have a printable character, then move everything
* after the current location back, and insert the character.
*/
if (isprint(ch)) {
if (buf_len < (MAX_BUF_LEN - 1)) {
for (i = buf_len; i > c; i--) buf[i] = buf[i-1];
buf[c] = (char) ch;
buf_len++;
c++;
redraw = TRUE;
}
}
if (op->debug) {
mvprintw(d->message->y, d->message->x,
"c: %3d ch: %04o (%d)", c, ch, ch);
wclrtoeol(nv_stdscr);
redraw = TRUE;
}
} while (ch != NV_NCURSES_ENTER);
/* free the message region */
nv_ncurses_destroy_region(d->message);
d->message = NULL;
curs_set(0); /* make the cursor invisible */
wrefresh(nv_stdscr);
free(tmp);
tmp = strdup(buf);
return tmp;
} /* nv_ncurses_get_input() */
/*
* nv_ncurses_message() - print a message
*/
static void nv_ncurses_message(Options *op, int level, const char *msg)
{
DataStruct *d = (DataStruct *) op->ui.priv;
int w, h, x, y, ch;
char *prefix;
if (!msg) return;
/* XXX for now, log messages are ignored by the ncurses ui */
if (level == NV_MSG_LEVEL_LOG) return;
/* determine the prefix for the message from the message level */
switch(level) {
case NV_MSG_LEVEL_MESSAGE:
prefix = NULL;
break;
case NV_MSG_LEVEL_WARNING:
prefix = "WARNING: ";
break;
case NV_MSG_LEVEL_ERROR:
prefix = "ERROR: ";
break;
default:
return;
}
draw_message:
if (d->message) {
/*
* XXX we may already have a message region allocated when we
* enter nv_ncurses_message(): we may have been in the middle
* of displaying a progress bar when we encountered an error
* that we need to report. To deal with this situation, throw
* out the existing message region; nv_ncurses_status_update()
* and nv_ncurses_status_end() will have to recreate their
* message region.
*/
nv_ncurses_destroy_region(d->message);
d->message = NULL;
}
/* create the message region and print msg in it */
nv_ncurses_do_message_region(d, prefix, msg, FALSE, 2);
/* init the dimensions of the button */
w = 6;
h = 1;
x = (d->message->w - w) / 2;
y = d->message->h - 2;
/* draw the OK button */
nv_ncurses_draw_button(d, d->message, x, y, w, h, "OK",
TRUE, FALSE);
wrefresh(nv_stdscr);
/* wait for enter */
do {
/* if a resize occurred, jump back to the top and redraw */
if (nv_ncurses_check_resize(d, FALSE)) goto draw_message;
ch = wgetch(nv_stdscr);
switch (ch) {
case NV_NCURSES_CTRL('L'):
nv_ncurses_check_resize(d, TRUE);
goto draw_message;
break;
}
} while (ch != NV_NCURSES_ENTER);
/* animate the button being pushed down */
nv_ncurses_erase_button(d->message, x, y, w, h);
nv_ncurses_draw_button(d, d->message, x, y, w, h, "OK", TRUE, TRUE);
wrefresh(nv_stdscr);
usleep(NV_NCURSES_BUTTON_PRESS_TIME);
nv_ncurses_erase_button(d->message, x, y, w, h);
nv_ncurses_draw_button(d, d->message, x, y, w, h, "OK", TRUE, FALSE);
wrefresh(nv_stdscr);
usleep(NV_NCURSES_BUTTON_PRESS_TIME);
/* free the message region */
nv_ncurses_destroy_region(d->message);
d->message = NULL;
wrefresh(nv_stdscr);
} /* nv_ncurses_message() */
/*
* nv_ncurses_command_output() - drop this on the floor, for now.
*/
static void nv_ncurses_command_output(Options *op, const char *msg)
{
/*
* XXX we don't currently display command output in the ncurses ui
*/
return;
} /* nv_ncurses_command_output() */
/*
* nv_ncurses_approve_command_list() - list all the commands that will be
* executed, and ask for approval.
*/
static int nv_ncurses_approve_command_list(Options *op, CommandList *cl,
const char *descr)
{
DataStruct *d = (DataStruct *) op->ui.priv;
char *commandlist, *question;
int ret, len;
const char *buttons[2] = {"Yes", "No"};
/* initialize the question string */
len = strlen(descr) + 256;
question = (char *) malloc(len + 1);
snprintf(question, len, "The following operations will be performed to "
"install the %s. Is this acceptable?", descr);
commandlist = nv_ncurses_create_command_list_text(d, cl);
ret = nv_ncurses_paged_prompt(op, question, "Proposed CommandList",
commandlist, buttons, 2, 0);
free(question);
free(commandlist);
return ret == 0;
} /* nv_ncurses_approve_command_list() */
/*
* nv_ncurses_yes_no() - ask the yes/no question 'msg' and return TRUE for
* yes, and FALSE for no.
*/
static int nv_ncurses_yes_no(Options *op, const int def, const char *msg)
{
const char *buttons[2] = { "Yes", "No" };
return (nv_ncurses_multiple_choice(op, msg, buttons, 2,
(def) ? 0 : 1) == 0);
} /* nv_ncurses_yes_no() */
/*
* nv_ncurses_multiple_choice() - display a question with multiple-choice
* buttons allowing the user to select a button corresponding to the desired
* response. Returns the index of the button selected from the passed-in
* buttons array.
*/
static int nv_ncurses_multiple_choice(Options *op, const char *question,
const char * const *buttons, int num_buttons,
int default_button)
{
return nv_ncurses_paged_prompt(op, question, NULL, NULL, buttons,
num_buttons, default_button);
} /* nv_ncurses_multiple_choice() */
/*
* nv_ncurses_status_begin() - initialize a progress bar
*/
static void nv_ncurses_status_begin(Options *op,
const char *title, const char *msg)
{
DataStruct *d = (DataStruct *) op->ui.priv;
nv_ncurses_check_resize(d, FALSE);
/* cache the progress bar title */
d->progress_title = strdup(title);
/* create the message region for use by the progress bar */
nv_ncurses_do_progress_bar_region(d);
/* write the one line msg (truncating it, if need be) */
nv_ncurses_do_progress_bar_message(d, msg, d->message->h - 3,
d->message->w);
wrefresh(nv_stdscr);
} /* nv_ncurses_status_begin() */
/*
* nv_ncurses_status_update() - update the progress bar
*/
static void nv_ncurses_status_update(Options *op, const float percent,
const char *msg)
{
int i, n, h, ch, w;
int p[4];
char v[4];
DataStruct *d = (DataStruct *) op->ui.priv;
int color_offset, color_flag;
char status_char;
/*
* if the message region was deleted or if the window was resized,
* redraw the entire progress bar region.
*/
if (nv_ncurses_check_resize(d, FALSE) || !d->message) {
if (d->message) nv_ncurses_destroy_region(d->message);
nv_ncurses_do_progress_bar_region(d);
}
/* temporarily set getch() to non-blocking mode */
nodelay(nv_stdscr, TRUE);
while ((ch = wgetch(nv_stdscr)) != ERR) {
/*
* if the user explicitly requested that the screen be
* redrawn by pressing CTRL-L, then also redraw the entire
* progress bar egion.
*/
if (ch == NV_NCURSES_CTRL('L')) {
nv_ncurses_check_resize(d, TRUE);
nv_ncurses_destroy_region(d->message);
nv_ncurses_do_progress_bar_region(d);
}
}
/* set getch() back to blocking mode */
nodelay(nv_stdscr, FALSE);
/* compute the percentage */
w = d->message->w - 2;
n = ((int) (percent * (float) w));
n = NV_MAX(n, 2);
init_position(p, d->message->w);
init_percentage_string(v, (int) (100.0 * percent));
h = d->message->h;
/* write the one line msg (truncating it, if need be) */
nv_ncurses_do_progress_bar_message(d, msg, h - 3, d->message->w - 2);
/* draw the progress bar */
if (d->use_color) {
color_offset = 0;
color_flag = NV_NCURSES_INPUT_COLOR;
status_char = ' ';
} else {
color_offset = 1;
color_flag = 0;
status_char = '-';
}
for (i = 1 + color_offset; i <= w - color_offset; i++) {
int reverse_flag = i > n - color_offset ? 0 : A_REVERSE;
mvwaddch(nv_stdscr, d->message->y + h - 2, d->message->x + i,
choose_char(i, p, v, ' ') | reverse_flag | color_flag);
}
for (i = 0; i < 4; i++) {
if (p[i] >= n + 1 - color_offset) {
mvwaddch(nv_stdscr, d->message->y + h - 2, d->message->x + p[i],
(v[i] ? v[i] : status_char) | color_flag);
}
}
wrefresh(nv_stdscr);
} /* nv_ncurses_status_update() */
static void nv_ncurses_update_indeterminate(Options *op, const char *msg)
{
DataStruct *d = op->ui.priv;
static uint32_t pattern = 0x7ff;
int x;
if (nv_ncurses_check_resize(d, FALSE) || !d->message) {
if (d->message) nv_ncurses_destroy_region(d->message);
nv_ncurses_do_progress_bar_region(d);
}
nv_ncurses_do_progress_bar_message(d, msg, d->message->h - 3,
d->message->w - 2);
for (x = 1; x < d->message->w - 1; x++) {
int color_flag;
if (d->use_color) {
color_flag = NV_NCURSES_INPUT_COLOR;
} else {
color_flag = 0;
}
if (pattern & (1 << (x % 32))) {
color_flag |= A_REVERSE;
}
mvwaddch(nv_stdscr, d->message->y + d->message->h - 2,
d->message->x + x, ' ' | color_flag);
}
wrefresh(nv_stdscr);
pattern = pattern << 1 | (pattern >> 31 & 1);
usleep(100000);
}
/*
* nv_ncurses_status_end() - draw the progress bar at 100%
*/
static void nv_ncurses_status_end(Options *op, const char *msg)
{
int i, n, h;
int p[4];
char v[4];
DataStruct *d = (DataStruct *) op->ui.priv;
/*
* if the message region was deleted or if the window was resized,
* redraw the entire progress bar region.
*/
if (nv_ncurses_check_resize(d, FALSE) || !d->message) {
if (d->message) nv_ncurses_destroy_region(d->message);
nv_ncurses_do_progress_bar_region(d);
}
n = d->message->w - 2;
init_position(p, d->message->w);
init_percentage_string(v, 100.0);
h = d->message->h;
/* write the one line msg (truncating it, if need be) */
nv_ncurses_do_progress_bar_message(d, msg, h - 3, d->message->w - 2);
/* draw the complete progress bar */
if (d->use_color) {
for (i = 1; i < (n+1); i++) {
mvwaddch(nv_stdscr, d->message->y + h - 2, d->message->x + i,
choose_char(i, p, v, ' ') |
A_REVERSE | NV_NCURSES_INPUT_COLOR);
}
} else {
for (i = 2; i < (n); i++) {
mvwaddch(nv_stdscr, d->message->y + h - 2, d->message->x + i,
choose_char(i, p, v, ' ') | A_REVERSE);
}
}
wrefresh(nv_stdscr);
free(d->progress_title);
d->progress_title = NULL;
/* XXX don't free the message window, yet... */
} /* nv_ncurses_status_end() */
/*
* nv_ncurses_close() - close the ui: free any memory we allocated,
* and end curses mode.
*/
static void nv_ncurses_close(Options *op)
{
DataStruct *d = NULL;
if (op) {
/* XXX op may be NULL if we get called from a signal handler */
d = (DataStruct *) op->ui.priv;
nv_ncurses_destroy_region(d->header);
nv_ncurses_destroy_region(d->footer);
free(d);
}
wclear(nv_stdscr);
wrefresh(nv_stdscr);
endwin(); /* End curses mode */
}
/****************************************************************************/
/*
* internal helper functions for manipulating the header and footer
*/
/*
* nv_ncurses_set_header() - write the title to the header region;
* note that this function does not call refresh()
*/
static void nv_ncurses_set_header(DataStruct *d, const char *title)
{
int x, y;
char *tmp;
tmp = strdup(title);
if (d->title) free(d->title);
d->title = tmp;
x = (d->header->w - strlen(d->title)) / 2;
y = 0;
wattrset(nv_stdscr, d->header->attr);
nv_ncurses_clear_region(d->header);
mvwaddstr(nv_stdscr, d->header->y + y, d->header->x + x, (char *) d->title);
wattrset(nv_stdscr, A_NORMAL);
} /* nv_ncurses_set_header() */
/*
* nv_ncurses_set_footer() - write the left and right text to the
* footer; note that this function does not call refresh()
*/
static void nv_ncurses_set_footer(DataStruct *d, const char *left,
const char *right)
{
int x, y;
char *tmp0, *tmp1;
tmp0 = strdup(left);
tmp1 = strdup(right);
if (d->footer_left) free(d->footer_left);
if (d->footer_right) free(d->footer_right);
d->footer_left = tmp0;
d->footer_right = tmp1;
wattrset(nv_stdscr, d->footer->attr);
nv_ncurses_clear_region(d->footer);
if (d->footer_left) {
y = 0;
x = 1;
mvwaddstr(nv_stdscr, d->footer->y + y, d->footer->x + x,
d->footer_left);
}
if (d->footer_right) {
y = 0;
x = d->footer->w - strlen(d->footer_right) - 1;
mvwaddstr(nv_stdscr, d->footer->y + y, d->footer->x + x,
d->footer_right);
}
wattrset(nv_stdscr, A_NORMAL);
} /* nv_ncurses_set_footer() */
/****************************************************************************/
/*
* internal helper functions for manipulating RegionStructs
*/
/*
* nv_ncurses_create_region() - create a new region at the specified
* location with the specified dimensions; note that this function
* does not call refresh()
*/
static RegionStruct *nv_ncurses_create_region(DataStruct *d,
int x, int y, int w, int h,
int color,
int no_color_attr)
{
RegionStruct *region =
(RegionStruct *) malloc(sizeof(RegionStruct));
region->x = x;
region->y = y;
region->w = w;
region->h = h;
if (d->use_color) region->attr = color;
else region->attr = no_color_attr;
/* create a single line for use in clearing the region */
region->line = (char *) malloc(w + 1);
memset(region->line, ' ', w);
region->line[w] = '\0';
/* clear the region */
wattrset(nv_stdscr, region->attr);
nv_ncurses_clear_region(region);
wattrset(nv_stdscr, A_NORMAL);
return region;
} /* nv_ncurses_create_region() */
/*
* nv_ncurses_clear_region() - clear each line in the region; note
* that this function does not call refresh(), nor does it explicitly
* set any attributes.
*/
static void nv_ncurses_clear_region(RegionStruct *region)
{
int i;
for (i = region->y; i < (region->y + region->h); i++) {
mvwaddstr(nv_stdscr, i, region->x, region->line);
}
} /* nv_ncurses_clear_region() */
/*
* nv_ncurses_destroy_region() - clear and free the RegionStruct; note
* that this function does not call refresh()
*/
static void nv_ncurses_destroy_region(RegionStruct *region)
{
if (!region) return;
wattrset(nv_stdscr, A_NORMAL);
nv_ncurses_clear_region(region);
free(region->line);
free(region);
} /* nv_ncurses_destroy_region() */
/****************************************************************************/
/*
* internal helper functions for drawing buttons
*/
/*
* nv_ncurses_draw_button() - draw a button on the specified region,
* at location x,y with dimensions w,h. Give the button the label
* str.
*
* The hilite parameter, when TRUE, causes the label to be printed in
* reverse colors.
*
* The down parameter, when TRUE, causes the button to be drawn as if
* it had been pressed down (ie: shifted down and right).
*/
static void nv_ncurses_draw_button(DataStruct *d, RegionStruct *region,
int x, int y, int w, int h,
const char *str, bool hilite, bool down)
{
int i, j, n, attr = 0;
if (down) x++, y++;
n = strlen(str);
n = (n > w) ? 0 : ((w - n) / 2);
if (d->use_color) {
attr = NV_NCURSES_BUTTON_COLOR;
} else if (hilite) {
attr = A_REVERSE;
} else {
attr = 0;
}
for (j = y; j < (y + h); j++) {
for (i = x; i < (x + w); i++) {
mvwaddch(nv_stdscr, region->y + j, region->x + i, ' ' | attr);
}
}
if (hilite) attr |= A_REVERSE;
wattron(nv_stdscr, attr);
mvwaddstr(nv_stdscr, region->y + y + h/2, region->x + x + n, str);
wattroff(nv_stdscr, attr);
} /* nv_ncurses_draw_button() */
/*
* nv_ncurses_erase_button() - erase the button on the specified
* region at location x,y with dimensions w,h.
*/
static void nv_ncurses_erase_button(RegionStruct *region,
int x, int y, int w, int h)
{
int i, j;
for (j = y; j <= (y + h); j++) {
for (i = x; i <= (x + w); i++) {
mvwaddch(nv_stdscr, region->y + j, region->x + i,
' ' | region->attr);
}
}
} /* nv_ncurses_erase_button() */
/*****************************************************************************/
/*
* nv_ncurses_do_message_region(),
* nv_ncurses_do_progress_bar_region(),
* nv_ncurses_do_progress_bar_message() - helper functions for drawing
* regions and stuff.
*/
/*
* nv_ncurses_do_message_region() - create a new message region
* containing the string msg. The "top" argument indicates whether
* the region should be vertically positioned immediately below the
* header, or should be positioned 1/3 of the way down the screen.
* The num_extra_lines argument is used to request extra lines in the
* message region below the string (to leave room for buttons, for
* example).
*/
static void nv_ncurses_do_message_region(DataStruct *d, const char *prefix,
const char *msg, int top,
int num_extra_lines)
{
int w, h, x, y;
TextRows *t;
/*
* compute the width and height that we need (taking into account
* num_extra_lines that the caller may need for buttons
*/
w = d->width - 2;
t = d->format_text_rows(prefix, msg, w - 2, TRUE);
h = t->n + num_extra_lines + 2;
/*
* compute the starting position of the message region: either
* immediately below the header or 1/3 of the way down the screen.
*/
x = 1;
if (top) y = 2;
else y = ((d->height - (3 + h)) / 3) + 1;
/* create the message region */
d->message = nv_ncurses_create_region(d, x, y, w, h,
NV_NCURSES_MESSAGE_COLOR,
NV_NCURSES_MESSAGE_NO_COLOR);
nv_ncurses_format_print(d, d->message, 1, 1, d->message->w - 2,
d->message->h - (1 + num_extra_lines), t);
/* free the text rows */
nv_ncurses_free_text_rows(t);
} /* nv_ncurses_do_message_region() */
/*
* nv_ncurses_do_progress_bar_region() - create a message region, draw
* the progress bar's title, separator, and the empty progress bar.
*/
static void nv_ncurses_do_progress_bar_region(DataStruct *d)
{
int n, h, i, p[4];
char v[4];
/* create the message region and print the title in it */
nv_ncurses_do_message_region(d, NULL, d->progress_title, FALSE, 3);
n = d->message->w - 2;
h = d->message->h;
wattrset(nv_stdscr, d->message->attr);
/* draw the horizontal separator */
for (i = 1; i <= n; i++) {
mvwaddch(nv_stdscr, d->message->y + h - 4, d->message->x + i,
NV_NCURSES_HLINE | d->message->attr);
}
/* draw an empty progress bar */
init_position(p, n + 2);
init_percentage_string(v, 0);
v[2] = '0';
if (d->use_color) {
for (i = 1; i <= n; i++) {
mvwaddch(nv_stdscr, d->message->y + h - 2, d->message->x + i,
choose_char(i, p, v, ' ') | NV_NCURSES_INPUT_COLOR);
}
} else {
mvwaddch(nv_stdscr, d->message->y + h - 2, d->message->x + 1, '[');
for (i = 2; i < n; i++)
mvwaddch(nv_stdscr, d->message->y + h - 2, d->message->x + i,
choose_char(i, p, v, '-'));
mvwaddch(nv_stdscr, d->message->y + h - 2, d->message->x + n, ']');
}
} /* nv_ncurses_do_progress_bar_region() */
/*
* nv_ncurses_do_progress_bar_message() - write the one line progress
* bar message to the message region, truncating the string, if need
* be.
*/
static void nv_ncurses_do_progress_bar_message(DataStruct *d, const char *str,
int y, int w)
{
char *tmp;
/* clear the message line */
wattrset(nv_stdscr, d->message->attr);
mvwaddstr(nv_stdscr, d->message->y + y, d->message->x, d->message->line);
/* write the message string */
if (str) {
tmp = malloc(w + 1);
strncpy(tmp, str, w);
tmp[w] = '\0';
mvwaddstr(nv_stdscr, d->message->y + y, d->message->x + 1, tmp);
free(tmp);
}
} /* nv_ncurses_do_progress_bar_message() */
/***************************************************************************/
/*
* pager functions
*/
/*
* pager functions -- these functions provide the basic behavior of a
* text viewer... used to display TextRows.
*
* d : DataStruct struct
* x : starting x coordinate of the pager
* y : starting y coordinate of the pager
* w : width of the pager
* h : height of the pager
* t : TextRows to be displayed
* label : string to be displayed in the status bar
* cur : initial current line of the pager
*/
static PagerStruct *nv_ncurses_create_pager(DataStruct *d,
int x, int y, int w, int h,
TextRows *t, const char *label,
int cur)
{
PagerStruct *p = (PagerStruct *) malloc(sizeof(PagerStruct));
p->t = t;
p->region = nv_ncurses_create_region(d, x, y, w, h, A_NORMAL, A_NORMAL);
p->label = label;
p->cur = cur;
p->page = h - 2;
nv_ncurses_pager_update(d, p);
return p;
} /* nv_ncurses_create_pager() */
/*
* nv_ncurses_destroy_pager() - free resources associated with the
* pager
*/
static void nv_ncurses_destroy_pager(PagerStruct *p)
{
nv_ncurses_destroy_region(p->region);
free(p);
} /* nv_ncurses_destroy_pager () */
/*
* nv_ncurses_pager_update() - redraw the text in the pager, and
* update the information about the pager in the footer. Note that
* this function does not call refresh().
*/
static void nv_ncurses_pager_update(DataStruct *d, PagerStruct *p)
{
int i, maxy, percent, denom;
char tmp[10];
if (!p) return;
/* determine the maximum y value for the text */
maxy = (p->cur + (p->region->h - 1));
if (maxy > p->t->n) maxy = p->t->n;
/* draw the text */
wattrset(nv_stdscr, p->region->attr);
for (i = p->cur; i < maxy; i++) {
mvwaddstr(nv_stdscr, p->region->y + i - p->cur, p->region->x,
p->region->line);
if (p->t->t[i]) {
mvwaddstr(nv_stdscr, p->region->y + i - p->cur, p->region->x,
p->t->t[i]);
}
}
/* compute the percentage */
denom = p->t->n - (p->region->h - 1);
if (denom < 1) percent = 100;
else percent = ((100.0 * (float) (p->cur)) / (float) denom);
/* create the percentage string */
if (p->t->n <= (p->region->h - 1)) snprintf(tmp, 10, "All");
else if (percent <= 0) snprintf(tmp, 10, "Top");
else if (percent >= 100) snprintf(tmp, 10, "Bot");
else snprintf(tmp, 10, "%3d%%", percent);
/* update the status in the footer */
nv_ncurses_set_footer(d, p->label, tmp);
} /* nv_ncurses_pager_update() */
/*
* nv_ncurses_pager_handle_events() - process any keys that affect the
* pager.
*/
static void nv_ncurses_pager_handle_events(DataStruct *d,
PagerStruct *p, int ch)
{
int n;
if (!p) return;
n = p->t->n - (p->region->h - 1);
switch (ch) {
case KEY_UP:
if (p->cur > 0) {
p->cur--;
nv_ncurses_pager_update(d, p);
wrefresh(nv_stdscr);
}
break;
case KEY_DOWN:
if (p->cur < n) {
p->cur++;
nv_ncurses_pager_update(d, p);
wrefresh(nv_stdscr);
}
break;
case KEY_PPAGE:
if (p->cur > 0) {
p->cur -= p->page;
if (p->cur < 0) p->cur = 0;
nv_ncurses_pager_update(d, p);
wrefresh(nv_stdscr);
}
break;
case KEY_NPAGE:
if (p->cur < n) {
p->cur += p->page;
if (p->cur > n) p->cur = n;
nv_ncurses_pager_update(d, p);
wrefresh(nv_stdscr);
}
break;
}
} /* nv_ncurses_pager_handle_events() */
static void draw_buttons(DataStruct *d, const char * const *buttons,
int num_buttons, int button, int button_w,
const int *buttons_x, int button_y)
{
int i;
for (i = 0; i < num_buttons; i++) {
nv_ncurses_draw_button(d, d->message, buttons_x[i], button_y,
button_w, 1, buttons[i], button == i, FALSE);
}
}
/*
* nv_ncurses_paged_prompt() - display a question with multiple-choice buttons
* along with a scrollable paged text area, allowing the user to review the
* pageable text and select a button corresponding to the desired response.
* Returns the index of the button selected from the passed-in buttons array.
*/
static int nv_ncurses_paged_prompt(Options *op, const char *question,
const char *pager_title,
const char *pager_text,
const char * const *buttons,
int num_buttons, int default_button)
{
DataStruct *d = (DataStruct *) op->ui.priv;
TextRows *t_pager = NULL;
int ch, cur = 0;
int i, button_w = 0, button_y, button = default_button;
int buttons_x[num_buttons];
PagerStruct *p = NULL;
/* check if the window was resized */
nv_ncurses_check_resize(d, FALSE);
for (i = 0; i < num_buttons; i++) {
int len = strlen(buttons[i]);
if (len > button_w) {
button_w = len;
}
}
button_w += 4;
print_message:
/* free any existing message region, pager, and pager textrows */
if (d->message) {
/*
* XXX we may already have a message region allocated when we
* enter nv_ncurses_paged_prompt(): we may have been in the middle
* of displaying a progress bar when we encountered an error
* that we need to report. To deal with this situation, throw
* out the existing message region; nv_ncurses_status_update()
* and nv_ncurses_status_end() will have to recreate their
* message region.
*/
nv_ncurses_destroy_region(d->message);
d->message = NULL;
}
if (p) {
nv_ncurses_destroy_pager(p);
p = NULL;
}
if (t_pager) {
nv_ncurses_free_text_rows(t_pager);
t_pager = NULL;
}
/* create the message region and print the question in it */
nv_ncurses_do_message_region(d, NULL, question,
(pager_title && pager_text), 2);
button_y = d->message->h - 2;
for (i = 0; i < num_buttons; i++) {
buttons_x[i] = (i + 1) * (d->message->w / (num_buttons + 1)) -
button_w / 2;
}
draw_buttons(d, buttons, num_buttons, button, button_w, buttons_x,
button_y);
/* draw the paged text */
if (pager_title && pager_text) {
t_pager = d->format_text_rows(NULL, pager_text, d->message->w, TRUE);
p = nv_ncurses_create_pager(d, 1, d->message->h + 2, d->message->w,
d->height - d->message->h - 4, t_pager,
pager_title, cur);
}
wrefresh(nv_stdscr);
/* process key strokes */
do {
/* if a resize occurred, jump back to the top and redraw */
if (nv_ncurses_check_resize(d, FALSE)) {
if (p) {
cur = p->cur;
}
goto print_message;
}
ch = wgetch(nv_stdscr);
switch (ch) {
case NV_NCURSES_TAB:
case KEY_RIGHT:
button = (button + 1) % num_buttons;
draw_buttons(d, buttons, num_buttons, button, button_w,
buttons_x, button_y);
wrefresh(nv_stdscr);
break;
case KEY_LEFT:
button = (button + num_buttons - 1) % num_buttons;
draw_buttons(d, buttons, num_buttons, button, button_w,
buttons_x, button_y);
wrefresh(nv_stdscr);
break;
case NV_NCURSES_CTRL('L'):
nv_ncurses_check_resize(d, TRUE);
if (p) {
cur = p->cur;
}
goto print_message;
break;
default:
break;
}
if (p) {
nv_ncurses_pager_handle_events(d, p, ch);
}
} while (ch != NV_NCURSES_ENTER);
/* animate the button being pushed down */
nv_ncurses_erase_button(d->message, buttons_x[button], button_y,
button_w, 1);
nv_ncurses_draw_button(d, d->message, buttons_x[button], button_y,
button_w, 1, buttons[button], TRUE, TRUE);
wrefresh(nv_stdscr);
usleep(NV_NCURSES_BUTTON_PRESS_TIME);
nv_ncurses_erase_button(d->message, buttons_x[button], button_y,
button_w, 1);
nv_ncurses_draw_button(d, d->message, buttons_x[button], button_y,
button_w, 1, buttons[button], TRUE, FALSE);
wrefresh(nv_stdscr);
usleep(NV_NCURSES_BUTTON_PRESS_TIME);
/* restore the footer */
nv_ncurses_set_footer(d, NV_NCURSES_DEFAULT_FOOTER_LEFT,
NV_NCURSES_DEFAULT_FOOTER_RIGHT);
/* clean up */
if (t_pager) {
nv_ncurses_free_text_rows(t_pager);
}
if (p) {
nv_ncurses_destroy_pager(p);
}
nv_ncurses_destroy_region(d->message);
d->message = NULL;
wrefresh(nv_stdscr);
return button;
}
/*****************************************************************************/
/*
* helper functions for the progress bar
*/
static int choose_char(int i, int p[4], char v[4], char def)
{
if (p[0] == i) return v[0] ? v[0] : def;
if (p[1] == i) return v[1] ? v[1] : def;
if (p[2] == i) return v[2] ? v[2] : def;
if (p[3] == i) return v[3] ? v[3] : def;
return def;
}
static void init_percentage_string(char v[4], int n)
{
int j;
n = NV_MAX(n, 1);
v[0] = (n/100);
v[1] = (n/10) - (v[0]*10);
v[2] = (n - (v[0]*100) - (v[1]*10));
v[0] += '0';
v[1] += '0';
v[2] += '0';
v[3] = '%';
for (j = 0; j < 3; j++) {
if (v[j] == '0') v[j] = 0;
else break;
}
}
static void init_position(int p[4], int w)
{
p[0] = 0 + (w - 4)/2;
p[1] = 1 + (w - 4)/2;
p[2] = 2 + (w - 4)/2;
p[3] = 3 + (w - 4)/2;
}
/*****************************************************************************/
/*
* misc helper functions
*/
/*
* nv_ncurses_create_command_list_text() - build a string from the command list
*/
static char *nv_ncurses_create_command_list_text(DataStruct *d, CommandList *cl)
{
char *ret = strdup("");
int i;
for (i = 0; i < cl->num; i++) {
const char *str = cl->descriptions[i];
if (str) {
int lenret, lenstr;
char *tmp;
lenret = strlen(ret);
lenstr = strlen(str);
tmp = malloc(lenret + lenstr + 2 /* "\n\0" */);
tmp[0] = 0;
memcpy(tmp, ret, lenret);
memcpy(tmp + lenret, str, lenstr);
tmp[lenret + lenstr] = '\n';
tmp[lenret + lenstr + 1] = 0;
free(ret);
ret = tmp;
}
}
return ret;
}
/*
* nv_free_text_rows() - free the TextRows data structure allocated by
* nv_format_text_rows()
*/
static void nv_ncurses_free_text_rows(TextRows *t)
{
int i;
if (!t) return;
for (i = 0; i < t->n; i++) free(t->t[i]);
if (t->t) free(t->t);
free(t);
} /* nv_free_text_rows() */
/*
* nv_ncurses_formatted_printw() - this function formats the string
* str on the region region, wrapping to the next line as necessary,
* using the bounding box specified by x, y, w, and h.
*
* The number of lines printed are returned.
*/
static int nv_ncurses_format_print(DataStruct *d, RegionStruct *region,
int x, int y, int w, int h,
TextRows *t)
{
int i, n;
n = NV_MIN(t->n, h);
wattrset(nv_stdscr, region->attr);
for (i = 0; i < n; i++) {
mvwaddstr(nv_stdscr, region->y + y + i, region->x + x, t->t[i]);
}
wattrset(nv_stdscr, A_NORMAL);
return n;
} /* nv_ncurses_format_printw() */
/*
* nv_ncurses_check_resize() - check if the dimensions of the screen
* have changed; if so, update the old header and footer regions,
* clear everything, update our cached dimensions, and recreate the
* header and footer.
*
* XXX we could catch the SIGWINCH (window resize) signal, but that's
* asynchronous... it's safer to only attempt to handle a resize when
* we know we can.
*/
static int nv_ncurses_check_resize(DataStruct *d, bool force)
{
int x, y;
getmaxyx(nv_stdscr, y, x);
if (!force) {
if ((x == d->width) && (y == d->height)) {
/* no resize detected... just return */
return FALSE;
}
}
/* we have been resized */
/* destroy the old header and footer */
nv_ncurses_destroy_region(d->header);
nv_ncurses_destroy_region(d->footer);
wclear(nv_stdscr);
/* update our cached copy of the dimensions */
d->height = y;
d->width = x;
/* recreate the header and footer */
d->header = nv_ncurses_create_region(d, 1, 0, d->width - 2, 1,
NV_NCURSES_HEADER_COLOR,
NV_NCURSES_HEADER_NO_COLOR);
d->footer = nv_ncurses_create_region(d, 1, d->height - 2, d->width - 2, 1,
NV_NCURSES_FOOTER_COLOR,
NV_NCURSES_FOOTER_NO_COLOR);
nv_ncurses_set_header(d, d->title);
nv_ncurses_set_footer(d, d->footer_left, d->footer_right);
return TRUE;
} /* nv_ncurses_check_resize() */