mirror of
https://github.com/NVIDIA/nvidia-installer.git
synced 2025-07-23 10:23:00 +02:00
2048 lines
53 KiB
C
2048 lines
53 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 int nv_ncurses_display_license (Options*, 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_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 char *nv_ncurses_mode_to_permission_string(mode_t);
|
|
|
|
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_display_license,
|
|
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_close
|
|
};
|
|
|
|
|
|
|
|
|
|
/*
|
|
* nv_ncurses_detect() - initialize ncurses; return FALSE if
|
|
* initialization fails
|
|
*/
|
|
|
|
static int nv_ncurses_detect(Options *op)
|
|
{
|
|
int x, y;
|
|
|
|
if (!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(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);
|
|
}
|
|
}
|
|
|
|
clear(); /* 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(stdscr, TRUE); /* enable keypad, function keys, arrow keys, etc */
|
|
|
|
getmaxyx(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);
|
|
|
|
refresh();
|
|
|
|
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);
|
|
|
|
refresh();
|
|
|
|
} /* nv_ncurses_set_title() */
|
|
|
|
|
|
|
|
|
|
/*
|
|
* nv_ncurses_get_input - prompt for user input with the given msg;
|
|
* returns the user inputed 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);
|
|
|
|
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++) {
|
|
mvaddch(input_y, input_x + i, BUF_CHAR(buf[i + x]) | color);
|
|
}
|
|
|
|
/* if we're scrolling, display an arrow */
|
|
|
|
if (x > 0) {
|
|
mvaddch(input_y, input_x - 1, '<' | d->message->attr);
|
|
} else {
|
|
mvaddch(input_y, input_x - 1, ' ' | d->message->attr);
|
|
}
|
|
|
|
if (buf_len > (input_len - 1 + x)) {
|
|
mvaddch(input_y, input_x + input_len, '>' | d->message->attr);
|
|
} else {
|
|
mvaddch(input_y, input_x + input_len, ' ' | d->message->attr);
|
|
}
|
|
|
|
redraw = FALSE;
|
|
}
|
|
|
|
/* position the cursor */
|
|
|
|
move(input_y, input_x - x + c);
|
|
refresh();
|
|
|
|
/* wait for input */
|
|
|
|
if (nv_ncurses_check_resize(d, FALSE)) goto draw_get_input;
|
|
ch = getch();
|
|
|
|
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);
|
|
clrtoeol();
|
|
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 */
|
|
refresh();
|
|
|
|
free(tmp);
|
|
tmp = strdup(buf);
|
|
return tmp;
|
|
|
|
} /* nv_ncurses_get_input() */
|
|
|
|
|
|
|
|
|
|
/*
|
|
* nv_ncurses_display_license() - print the text from the license file,
|
|
* prompt for acceptance from the user, and return whether or not the
|
|
* user accepted.
|
|
*/
|
|
|
|
static int nv_ncurses_display_license(Options *op, const char *license)
|
|
{
|
|
static const char *descr = "Please read the following LICENSE "
|
|
"and then select either \"Accept\" to accept the license "
|
|
"and continue with the installation, or select "
|
|
"\"Do Not Accept\" to abort the installation.";
|
|
|
|
static const char *buttons[2] = {"Accept", "Do Not Accept"};
|
|
|
|
int ret = nv_ncurses_paged_prompt(op, descr, "NVIDIA Software License",
|
|
license, buttons, 2, 1);
|
|
|
|
return ret == 0;
|
|
} /* nv_ncurses_display_license() */
|
|
|
|
|
|
|
|
|
|
/*
|
|
* 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);
|
|
refresh();
|
|
|
|
/* 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 = getch();
|
|
|
|
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);
|
|
refresh();
|
|
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);
|
|
refresh();
|
|
usleep(NV_NCURSES_BUTTON_PRESS_TIME);
|
|
|
|
/* free the message region */
|
|
|
|
nv_ncurses_destroy_region(d->message);
|
|
d->message = NULL;
|
|
refresh();
|
|
|
|
} /* 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);
|
|
|
|
refresh();
|
|
|
|
} /* 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;
|
|
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);
|
|
}
|
|
|
|
/* temporarily set getch() to non-blocking mode */
|
|
|
|
nodelay(stdscr, TRUE);
|
|
|
|
while ((ch = getch()) != ERR) {
|
|
/*
|
|
* if the user explicitely 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(stdscr, FALSE);
|
|
|
|
/* compute the percentage */
|
|
|
|
n = ((int) (percent * (float) (d->message->w - 2)));
|
|
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) {
|
|
for (i = 1; i <= n; i++) {
|
|
mvaddch(d->message->y + h - 2, d->message->x + i,
|
|
choose_char(i, p, v,' ') |
|
|
A_REVERSE | NV_NCURSES_INPUT_COLOR);
|
|
}
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
if (p[i] >= (n+1)) {
|
|
mvaddch(d->message->y + h - 2, d->message->x + p[i],
|
|
(v[i] ? v[i] : ' ') | NV_NCURSES_INPUT_COLOR);
|
|
}
|
|
}
|
|
} else {
|
|
for (i = 2; i < n; i++) {
|
|
mvaddch(d->message->y + h - 2, d->message->x + i,
|
|
choose_char(i, p, v, ' ') | A_REVERSE);
|
|
}
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
if (p[i] >= n) {
|
|
mvaddch(d->message->y + h - 2, d->message->x + p[i],
|
|
v[i] ? v[i] : '-');
|
|
}
|
|
}
|
|
}
|
|
|
|
refresh();
|
|
|
|
} /* nv_ncurses_status_update() */
|
|
|
|
|
|
|
|
|
|
/*
|
|
* 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++) {
|
|
mvaddch(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++) {
|
|
mvaddch(d->message->y + h - 2, d->message->x + i,
|
|
choose_char(i, p, v, ' ') | A_REVERSE);
|
|
}
|
|
}
|
|
|
|
refresh();
|
|
|
|
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);
|
|
}
|
|
|
|
clear();
|
|
refresh();
|
|
|
|
endwin(); /* End curses mode */
|
|
|
|
return;
|
|
|
|
} /* nv_ncurses_close() */
|
|
|
|
|
|
|
|
/****************************************************************************/
|
|
/*
|
|
* 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;
|
|
|
|
attrset(d->header->attr);
|
|
|
|
nv_ncurses_clear_region(d->header);
|
|
mvaddstr(d->header->y + y, d->header->x + x, (char *) d->title);
|
|
attrset(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;
|
|
|
|
attrset(d->footer->attr);
|
|
|
|
nv_ncurses_clear_region(d->footer);
|
|
|
|
if (d->footer_left) {
|
|
y = 0;
|
|
x = 1;
|
|
mvaddstr(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;
|
|
mvaddstr(d->footer->y + y, d->footer->x + x, d->footer_right);
|
|
}
|
|
|
|
attrset(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 */
|
|
|
|
attrset(region->attr);
|
|
nv_ncurses_clear_region(region);
|
|
attrset(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++) {
|
|
mvaddstr(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;
|
|
|
|
attrset(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++) {
|
|
mvaddch(region->y + j, region->x + i, ' ' | attr);
|
|
}
|
|
}
|
|
|
|
if (hilite) attr |= A_REVERSE;
|
|
|
|
attron(attr);
|
|
mvaddstr(region->y + y + h/2, region->x + x + n, str);
|
|
attroff(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++) {
|
|
mvaddch(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;
|
|
|
|
attrset(d->message->attr);
|
|
|
|
/* draw the horizontal separator */
|
|
|
|
for (i = 1; i <= n; i++) {
|
|
mvaddch(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++) {
|
|
mvaddch(d->message->y + h - 2, d->message->x + i,
|
|
choose_char(i, p, v, ' ') | NV_NCURSES_INPUT_COLOR);
|
|
}
|
|
} else {
|
|
mvaddch(d->message->y + h - 2, d->message->x + 1, '[');
|
|
for (i = 2; i < n; i++)
|
|
mvaddch(d->message->y + h - 2, d->message->x + i,
|
|
choose_char(i, p, v, '-'));
|
|
mvaddch(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 */
|
|
|
|
attrset(d->message->attr);
|
|
mvaddstr(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';
|
|
mvaddstr(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 */
|
|
|
|
attrset(p->region->attr);
|
|
|
|
for (i = p->cur; i < maxy; i++) {
|
|
mvaddstr(p->region->y + i - p->cur, p->region->x, p->region->line);
|
|
if (p->t->t[i]) {
|
|
mvaddstr(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);
|
|
refresh();
|
|
}
|
|
break;
|
|
|
|
case KEY_DOWN:
|
|
if (p->cur < n) {
|
|
p->cur++;
|
|
nv_ncurses_pager_update(d, p);
|
|
refresh();
|
|
}
|
|
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);
|
|
refresh();
|
|
}
|
|
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);
|
|
refresh();
|
|
}
|
|
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);
|
|
}
|
|
|
|
refresh();
|
|
|
|
/* 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 = getch();
|
|
|
|
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);
|
|
refresh();
|
|
break;
|
|
|
|
case KEY_LEFT:
|
|
button = (button + num_buttons - 1) % num_buttons;
|
|
draw_buttons(d, buttons, num_buttons, button, button_w,
|
|
buttons_x, button_y);
|
|
refresh();
|
|
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);
|
|
refresh();
|
|
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);
|
|
refresh();
|
|
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;
|
|
|
|
refresh();
|
|
|
|
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)
|
|
{
|
|
int i, len;
|
|
Command *c;
|
|
char *str, *perms, *ret = strdup("");
|
|
|
|
for (i = 0; i < cl->num; i++) {
|
|
c = &cl->cmds[i];
|
|
|
|
str = NULL;
|
|
|
|
switch (c->cmd) {
|
|
|
|
case INSTALL_CMD:
|
|
perms = nv_ncurses_mode_to_permission_string(c->mode);
|
|
len = strlen(c->s0) + strlen(c->s1) + strlen(perms) + 64;
|
|
if (c->s2) {
|
|
len += strlen(c->s2) + 64;
|
|
}
|
|
str = (char *) malloc(len + 1);
|
|
snprintf(str, len, "Install the file '%s' as '%s' with "
|
|
"permissions '%s'", c->s0, c->s1, perms);
|
|
free(perms);
|
|
if (c->s2) {
|
|
len = strlen(c->s2) + 64;
|
|
snprintf(str + strlen(str), len,
|
|
" then execute the command `%s`", c->s2);
|
|
}
|
|
break;
|
|
|
|
case RUN_CMD:
|
|
len = strlen(c->s0) + 64;
|
|
str = (char *) malloc(len + 1);
|
|
snprintf(str, len, "Execute the command `%s`", c->s0);
|
|
break;
|
|
|
|
case SYMLINK_CMD:
|
|
len = strlen(c->s0) + strlen(c->s1) + 64;
|
|
str = (char *) malloc(len + 1);
|
|
snprintf(str, len, "Create a symbolic link '%s' to '%s'",
|
|
c->s0, c->s1);
|
|
break;
|
|
|
|
case BACKUP_CMD:
|
|
len = strlen(c->s0) + 64;
|
|
str = (char *) malloc(len + 1);
|
|
snprintf(str, len, "Backup the file '%s'", c->s0);
|
|
break;
|
|
|
|
case DELETE_CMD:
|
|
len = strlen(c->s0) + 64;
|
|
str = (char *) malloc(len + 1);
|
|
snprintf(str, len, "Delete the file '%s'", c->s0);
|
|
break;
|
|
|
|
default:
|
|
/* XXX should not get here */
|
|
break;
|
|
}
|
|
|
|
if (str) {
|
|
int lenret, lenstr;
|
|
char *tmp;
|
|
|
|
lenret = strlen(ret);
|
|
lenstr = strlen(str);
|
|
tmp = malloc(lenret + lenstr + 2 /* "\n\0" */);
|
|
|
|
tmp[0] = 0;
|
|
|
|
strncat(tmp, ret, lenret);
|
|
strncat(tmp, str, lenstr);
|
|
|
|
tmp[lenret + lenstr] = '\n';
|
|
tmp[lenret + lenstr + 1] = 0;
|
|
|
|
free(str);
|
|
free(ret);
|
|
ret = tmp;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* mode_to_permission_string() - given a mode bitmask, allocate and
|
|
* write a permission string.
|
|
*/
|
|
|
|
static char *nv_ncurses_mode_to_permission_string(mode_t mode)
|
|
{
|
|
char *s = (char *) malloc(10);
|
|
memset (s, '-', 9);
|
|
|
|
if (mode & (1 << 8)) s[0] = 'r';
|
|
if (mode & (1 << 7)) s[1] = 'w';
|
|
if (mode & (1 << 6)) s[2] = 'x';
|
|
|
|
if (mode & (1 << 5)) s[3] = 'r';
|
|
if (mode & (1 << 4)) s[4] = 'w';
|
|
if (mode & (1 << 3)) s[5] = 'x';
|
|
|
|
if (mode & (1 << 2)) s[6] = 'r';
|
|
if (mode & (1 << 1)) s[7] = 'w';
|
|
if (mode & (1 << 0)) s[8] = 'x';
|
|
|
|
s[9] = '\0';
|
|
return s;
|
|
|
|
} /* mode_to_permission_string() */
|
|
|
|
|
|
|
|
|
|
/*
|
|
* 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);
|
|
|
|
attrset(region->attr);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
mvaddstr(region->y + y + i, region->x + x, t->t[i]);
|
|
}
|
|
|
|
attrset(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(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);
|
|
|
|
clear();
|
|
|
|
/* 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() */
|