blob: b8cb06fde49f2114ecb90166b823ba0fa97a9519 [file] [log] [blame]
/* ----------------------------------------------------------------------- *
*
* Copyright 2004-2008 H. Peter Anvin - All Rights Reserved
* Copyright 2009-2014 Intel Corporation; author: H. Peter Anvin
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston MA 02110-1301, USA; either version 2 of the License, or
* (at your option) any later version; incorporated herein by reference.
*
* ----------------------------------------------------------------------- */
/*
* menumain.c
*
* Simple menu system which displays a list and allows the user to select
* a command line and/or edit it.
*/
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <consoles.h>
#include <getkey.h>
#include <minmax.h>
#include <setjmp.h>
#include <limits.h>
#include <com32.h>
#include <core.h>
#include <syslinux/adv.h>
#include <syslinux/boot.h>
#include "menu.h"
/* The symbol "cm" always refers to the current menu across this file... */
static struct menu *cm;
const struct menu_parameter mparm[NPARAMS] = {
[P_WIDTH] = {"width", 0},
[P_MARGIN] = {"margin", 10},
[P_PASSWD_MARGIN] = {"passwordmargin", 3},
[P_MENU_ROWS] = {"rows", 12},
[P_TABMSG_ROW] = {"tabmsgrow", 18},
[P_CMDLINE_ROW] = {"cmdlinerow", 18},
[P_END_ROW] = {"endrow", -1},
[P_PASSWD_ROW] = {"passwordrow", 11},
[P_TIMEOUT_ROW] = {"timeoutrow", 20},
[P_HELPMSG_ROW] = {"helpmsgrow", 22},
[P_HELPMSGEND_ROW] = {"helpmsgendrow", -1},
[P_HSHIFT] = {"hshift", 0},
[P_VSHIFT] = {"vshift", 0},
[P_HIDDEN_ROW] = {"hiddenrow", -2},
};
/* These macros assume "cm" is a pointer to the current menu */
#define WIDTH (cm->mparm[P_WIDTH])
#define MARGIN (cm->mparm[P_MARGIN])
#define PASSWD_MARGIN (cm->mparm[P_PASSWD_MARGIN])
#define MENU_ROWS (cm->mparm[P_MENU_ROWS])
#define TABMSG_ROW (cm->mparm[P_TABMSG_ROW]+VSHIFT)
#define CMDLINE_ROW (cm->mparm[P_CMDLINE_ROW]+VSHIFT)
#define END_ROW (cm->mparm[P_END_ROW])
#define PASSWD_ROW (cm->mparm[P_PASSWD_ROW]+VSHIFT)
#define TIMEOUT_ROW (cm->mparm[P_TIMEOUT_ROW]+VSHIFT)
#define HELPMSG_ROW (cm->mparm[P_HELPMSG_ROW]+VSHIFT)
#define HELPMSGEND_ROW (cm->mparm[P_HELPMSGEND_ROW])
#define HSHIFT (cm->mparm[P_HSHIFT])
#define VSHIFT (cm->mparm[P_VSHIFT])
#define HIDDEN_ROW (cm->mparm[P_HIDDEN_ROW])
static char *pad_line(const char *text, int align, int width)
{
static char buffer[MAX_CMDLINE_LEN];
int n, p;
if (width >= (int)sizeof buffer)
return NULL; /* Can't do it */
n = strlen(text);
if (n >= width)
n = width;
memset(buffer, ' ', width);
buffer[width] = 0;
p = ((width - n) * align) >> 1;
memcpy(buffer + p, text, n);
return buffer;
}
/* Display an entry, with possible hotkey highlight. Assumes
that the current attribute is the non-hotkey one, and will
guarantee that as an exit condition as well. */
static void
display_entry(const struct menu_entry *entry, const char *attrib,
const char *hotattrib, int width)
{
const char *p = entry->displayname;
char marker;
if (!p)
p = "";
switch (entry->action) {
case MA_SUBMENU:
marker = '>';
break;
case MA_EXIT:
marker = '<';
break;
default:
marker = 0;
break;
}
if (marker)
width -= 2;
while (width) {
if (*p) {
if (*p == '^') {
p++;
if (*p && ((unsigned char)*p & ~0x20) == entry->hotkey) {
fputs(hotattrib, stdout);
putchar(*p++);
fputs(attrib, stdout);
width--;
}
} else {
putchar(*p++);
width--;
}
} else {
putchar(' ');
width--;
}
}
if (marker) {
putchar(' ');
putchar(marker);
}
}
static void draw_row(int y, int sel, int top, int sbtop, int sbbot)
{
int i = (y - 4 - VSHIFT) + top;
int dis = (i < cm->nentries) && is_disabled(cm->menu_entries[i]);
printf("\033[%d;%dH\1#1\016x\017%s ",
y, MARGIN + 1 + HSHIFT,
(i == sel) ? "\1#5" : dis ? "\2#17" : "\1#3");
if (i >= cm->nentries) {
fputs(pad_line("", 0, WIDTH - 2 * MARGIN - 4), stdout);
} else {
display_entry(cm->menu_entries[i],
(i == sel) ? "\1#5" : dis ? "\2#17" : "\1#3",
(i == sel) ? "\1#6" : dis ? "\2#17" : "\1#4",
WIDTH - 2 * MARGIN - 4);
}
if (cm->nentries <= MENU_ROWS) {
printf(" \1#1\016x\017");
} else if (sbtop > 0) {
if (y >= sbtop && y <= sbbot)
printf(" \1#7\016a\017");
else
printf(" \1#1\016x\017");
} else {
putchar(' '); /* Don't modify the scrollbar */
}
}
static jmp_buf timeout_jump;
int mygetkey(clock_t timeout)
{
clock_t t0, t;
clock_t tto, to;
int key;
if (!totaltimeout)
return get_key(stdin, timeout);
for (;;) {
tto = min(totaltimeout, INT_MAX);
to = timeout ? min(tto, timeout) : tto;
t0 = times(NULL);
key = get_key(stdin, to);
t = times(NULL) - t0;
if (totaltimeout <= t)
longjmp(timeout_jump, 1);
totaltimeout -= t;
if (key != KEY_NONE)
return key;
if (timeout) {
if (timeout <= t)
return KEY_NONE;
timeout -= t;
}
}
}
static int ask_passwd(const char *menu_entry)
{
char user_passwd[WIDTH], *p;
int done;
int key;
int x;
int rv;
printf("\033[%d;%dH\2#11\016l", PASSWD_ROW, PASSWD_MARGIN + 1);
for (x = 2; x <= WIDTH - 2 * PASSWD_MARGIN - 1; x++)
putchar('q');
printf("k\033[%d;%dHx", PASSWD_ROW + 1, PASSWD_MARGIN + 1);
for (x = 2; x <= WIDTH - 2 * PASSWD_MARGIN - 1; x++)
putchar(' ');
printf("x\033[%d;%dHm", PASSWD_ROW + 2, PASSWD_MARGIN + 1);
for (x = 2; x <= WIDTH - 2 * PASSWD_MARGIN - 1; x++)
putchar('q');
printf("j\017\033[%d;%dH\2#12 %s \033[%d;%dH\2#13",
PASSWD_ROW, (WIDTH - (strlen(cm->messages[MSG_PASSPROMPT]) + 2)) / 2,
cm->messages[MSG_PASSPROMPT], PASSWD_ROW + 1, PASSWD_MARGIN + 3);
drain_keyboard();
/* Actually allow user to type a password, then compare to the SHA1 */
done = 0;
p = user_passwd;
while (!done) {
key = mygetkey(0);
switch (key) {
case KEY_ENTER:
case KEY_CTRL('J'):
done = 1;
break;
case KEY_ESC:
case KEY_CTRL('C'):
p = user_passwd; /* No password entered */
done = 1;
break;
case KEY_BACKSPACE:
case KEY_DEL:
case KEY_DELETE:
if (p > user_passwd) {
printf("\b \b");
p--;
}
break;
case KEY_CTRL('U'):
while (p > user_passwd) {
printf("\b \b");
p--;
}
break;
default:
if (key >= ' ' && key <= 0xFF &&
(p - user_passwd) < WIDTH - 2 * PASSWD_MARGIN - 5) {
*p++ = key;
putchar('*');
}
break;
}
}
if (p == user_passwd)
return 0; /* No password entered */
*p = '\0';
rv = (cm->menu_master_passwd &&
passwd_compare(cm->menu_master_passwd, user_passwd))
|| (menu_entry && passwd_compare(menu_entry, user_passwd));
/* Clean up */
memset(user_passwd, 0, WIDTH);
drain_keyboard();
return rv;
}
static void draw_menu(int sel, int top, int edit_line)
{
int x, y;
int sbtop = 0, sbbot = 0;
const char *tabmsg;
int tabmsg_len;
if (cm->nentries > MENU_ROWS) {
int sblen = max(MENU_ROWS * MENU_ROWS / cm->nentries, 1);
sbtop = (MENU_ROWS - sblen + 1) * top / (cm->nentries - MENU_ROWS + 1);
sbbot = sbtop + sblen - 1;
sbtop += 4;
sbbot += 4; /* Starting row of scrollbar */
}
printf("\033[%d;%dH\1#1\016l", VSHIFT + 1, HSHIFT + MARGIN + 1);
for (x = 2 + HSHIFT; x <= (WIDTH - 2 * MARGIN - 1) + HSHIFT; x++)
putchar('q');
printf("k\033[%d;%dH\1#1x\017\1#2 %s \1#1\016x",
VSHIFT + 2,
HSHIFT + MARGIN + 1, pad_line(cm->title, 1, WIDTH - 2 * MARGIN - 4));
printf("\033[%d;%dH\1#1t", VSHIFT + 3, HSHIFT + MARGIN + 1);
for (x = 2 + HSHIFT; x <= (WIDTH - 2 * MARGIN - 1) + HSHIFT; x++)
putchar('q');
fputs("u\017", stdout);
for (y = 4 + VSHIFT; y < 4 + VSHIFT + MENU_ROWS; y++)
draw_row(y, sel, top, sbtop, sbbot);
printf("\033[%d;%dH\1#1\016m", y, HSHIFT + MARGIN + 1);
for (x = 2 + HSHIFT; x <= (WIDTH - 2 * MARGIN - 1) + HSHIFT; x++)
putchar('q');
fputs("j\017", stdout);
if (edit_line && cm->allowedit && !cm->menu_master_passwd)
tabmsg = cm->messages[MSG_TAB];
else
tabmsg = cm->messages[MSG_NOTAB];
tabmsg_len = strlen(tabmsg);
printf("\1#8\033[%d;%dH%s",
TABMSG_ROW, 1 + HSHIFT + ((WIDTH - tabmsg_len) >> 1), tabmsg);
printf("\1#0\033[%d;1H", END_ROW);
}
static void clear_screen(void)
{
fputs("\033e\033%@\033)0\033(B\1#0\033[?25l\033[2J", stdout);
}
static void display_help(const char *text)
{
int row;
const char *p;
if (!text) {
text = "";
printf("\1#0\033[%d;1H", HELPMSG_ROW);
} else {
printf("\2#16\033[%d;1H", HELPMSG_ROW);
}
for (p = text, row = HELPMSG_ROW; *p && row <= HELPMSGEND_ROW; p++) {
switch (*p) {
case '\r':
case '\f':
case '\v':
case '\033':
break;
case '\n':
printf("\033[K\033[%d;1H", ++row);
break;
default:
putchar(*p);
}
}
fputs("\033[K", stdout);
while (row <= HELPMSGEND_ROW) {
printf("\033[K\033[%d;1H", ++row);
}
}
static void show_fkey(int key)
{
int fkey;
while (1) {
switch (key) {
case KEY_F1:
fkey = 0;
break;
case KEY_F2:
fkey = 1;
break;
case KEY_F3:
fkey = 2;
break;
case KEY_F4:
fkey = 3;
break;
case KEY_F5:
fkey = 4;
break;
case KEY_F6:
fkey = 5;
break;
case KEY_F7:
fkey = 6;
break;
case KEY_F8:
fkey = 7;
break;
case KEY_F9:
fkey = 8;
break;
case KEY_F10:
fkey = 9;
break;
case KEY_F11:
fkey = 10;
break;
case KEY_F12:
fkey = 11;
break;
default:
fkey = -1;
break;
}
if (fkey == -1)
break;
if (cm->fkeyhelp[fkey].textname)
key = show_message_file(cm->fkeyhelp[fkey].textname,
cm->fkeyhelp[fkey].background);
else
break;
}
}
static const char *edit_cmdline(const char *input, int top)
{
static char cmdline[MAX_CMDLINE_LEN];
int key, len, prev_len, cursor;
int redraw = 1; /* We enter with the menu already drawn */
strlcpy(cmdline, input, MAX_CMDLINE_LEN);
cmdline[MAX_CMDLINE_LEN - 1] = '\0';
len = cursor = strlen(cmdline);
prev_len = 0;
for (;;) {
if (redraw > 1) {
/* Clear and redraw whole screen */
/* Enable ASCII on G0 and DEC VT on G1; do it in this order
to avoid confusing the Linux console */
clear_screen();
draw_menu(-1, top, 1);
prev_len = 0;
}
if (redraw > 0) {
/* Redraw the command line */
printf("\033[?25l\033[%d;1H\1#9> \2#10%s",
CMDLINE_ROW, pad_line(cmdline, 0, max(len, prev_len)));
printf("\2#10\033[%d;3H%s\033[?25h",
CMDLINE_ROW, pad_line(cmdline, 0, cursor));
prev_len = len;
redraw = 0;
}
key = mygetkey(0);
switch (key) {
case KEY_CTRL('L'):
redraw = 2;
break;
case KEY_ENTER:
case KEY_CTRL('J'):
return cmdline;
case KEY_ESC:
case KEY_CTRL('C'):
return NULL;
case KEY_BACKSPACE:
case KEY_DEL:
if (cursor) {
memmove(cmdline + cursor - 1, cmdline + cursor,
len - cursor + 1);
len--;
cursor--;
redraw = 1;
}
break;
case KEY_CTRL('D'):
case KEY_DELETE:
if (cursor < len) {
memmove(cmdline + cursor, cmdline + cursor + 1, len - cursor);
len--;
redraw = 1;
}
break;
case KEY_CTRL('U'):
if (len) {
len = cursor = 0;
cmdline[len] = '\0';
redraw = 1;
}
break;
case KEY_CTRL('W'):
if (cursor) {
int prevcursor = cursor;
while (cursor && my_isspace(cmdline[cursor - 1]))
cursor--;
while (cursor && !my_isspace(cmdline[cursor - 1]))
cursor--;
memmove(cmdline + cursor, cmdline + prevcursor,
len - prevcursor + 1);
len -= (prevcursor - cursor);
redraw = 1;
}
break;
case KEY_LEFT:
case KEY_CTRL('B'):
if (cursor) {
cursor--;
redraw = 1;
}
break;
case KEY_RIGHT:
case KEY_CTRL('F'):
if (cursor < len) {
putchar(cmdline[cursor++]);
}
break;
case KEY_CTRL('K'):
if (cursor < len) {
cmdline[len = cursor] = '\0';
redraw = 1;
}
break;
case KEY_HOME:
case KEY_CTRL('A'):
if (cursor) {
cursor = 0;
redraw = 1;
}
break;
case KEY_END:
case KEY_CTRL('E'):
if (cursor != len) {
cursor = len;
redraw = 1;
}
break;
case KEY_F1:
case KEY_F2:
case KEY_F3:
case KEY_F4:
case KEY_F5:
case KEY_F6:
case KEY_F7:
case KEY_F8:
case KEY_F9:
case KEY_F10:
case KEY_F11:
case KEY_F12:
show_fkey(key);
redraw = 1;
break;
default:
if (key >= ' ' && key <= 0xFF && len < MAX_CMDLINE_LEN - 1) {
if (cursor == len) {
cmdline[len] = key;
cmdline[++len] = '\0';
cursor++;
putchar(key);
prev_len++;
} else {
memmove(cmdline + cursor + 1, cmdline + cursor,
len - cursor + 1);
cmdline[cursor++] = key;
len++;
redraw = 1;
}
}
break;
}
}
}
static void print_timeout_message(int tol, int row, const char *msg)
{
static int last_msg_len = 0;
char buf[256];
int nc = 0, nnc, padc;
const char *tp = msg;
char tc;
char *tq = buf;
while ((size_t) (tq - buf) < (sizeof buf - 16) && (tc = *tp)) {
tp++;
if (tc == '#') {
nnc = sprintf(tq, "\2#15%d\2#14", tol);
tq += nnc;
nc += nnc - 8; /* 8 formatting characters */
} else if (tc == '{') {
/* Deal with {singular[,dual],plural} constructs */
struct {
const char *s, *e;
} tx[3];
const char *tpp;
int n = 0;
memset(tx, 0, sizeof tx);
tx[0].s = tp;
while (*tp && *tp != '}') {
if (*tp == ',' && n < 2) {
tx[n].e = tp;
n++;
tx[n].s = tp + 1;
}
tp++;
}
tx[n].e = tp;
if (*tp)
tp++; /* Skip final bracket */
if (!tx[1].s)
tx[1] = tx[0];
if (!tx[2].s)
tx[2] = tx[1];
/* Now [0] is singular, [1] is dual, and [2] is plural,
even if the user only specified some of them. */
switch (tol) {
case 1:
n = 0;
break;
case 2:
n = 1;
break;
default:
n = 2;
break;
}
for (tpp = tx[n].s; tpp < tx[n].e; tpp++) {
if ((size_t) (tq - buf) < (sizeof buf)) {
*tq++ = *tpp;
nc++;
}
}
} else {
*tq++ = tc;
nc++;
}
}
*tq = '\0';
if (nc >= last_msg_len) {
padc = 0;
} else {
padc = (last_msg_len - nc + 1) >> 1;
}
printf("\033[%d;%dH\2#14%*s%s%*s", row,
HSHIFT + 1 + ((WIDTH - nc) >> 1) - padc,
padc, "", buf, padc, "");
last_msg_len = nc;
}
/* Set the background screen, etc. */
static void prepare_screen_for_menu(void)
{
console_color_table = cm->color_table;
console_color_table_size = menu_color_table_size;
set_background(cm->menu_background);
}
static const char *do_hidden_menu(void)
{
int key;
int timeout_left, this_timeout;
clear_screen();
if (!setjmp(timeout_jump)) {
timeout_left = cm->timeout;
while (!cm->timeout || timeout_left) {
int tol = timeout_left / CLK_TCK;
print_timeout_message(tol, HIDDEN_ROW, cm->messages[MSG_AUTOBOOT]);
this_timeout = min(timeout_left, CLK_TCK);
key = mygetkey(this_timeout);
if (key != KEY_NONE) {
/* Clear the message from the screen */
print_timeout_message(0, HIDDEN_ROW, "");
return hide_key[key]; /* NULL if no MENU HIDEKEY in effect */
}
timeout_left -= this_timeout;
}
}
/* Clear the message from the screen */
print_timeout_message(0, HIDDEN_ROW, "");
if (cm->ontimeout)
return cm->ontimeout;
else
return cm->menu_entries[cm->defentry]->cmdline; /* Default entry */
}
static const char *run_menu(void)
{
int key;
int done = 0;
volatile int entry = cm->curentry;
int prev_entry = -1;
volatile int top = cm->curtop;
int prev_top = -1;
int clear = 1, to_clear;
const char *cmdline = NULL;
volatile clock_t key_timeout, timeout_left, this_timeout;
const struct menu_entry *me;
bool hotkey = false;
/* Note: for both key_timeout and timeout == 0 means no limit */
timeout_left = key_timeout = cm->timeout;
/* If we're in shiftkey mode, exit immediately unless a shift key
is pressed */
if (shiftkey && !shift_is_held()) {
return cm->menu_entries[cm->defentry]->cmdline;
} else {
shiftkey = 0;
}
/* Do this before hiddenmenu handling, so we show the background */
prepare_screen_for_menu();
/* Handle hiddenmenu */
if (hiddenmenu) {
cmdline = do_hidden_menu();
if (cmdline)
return cmdline;
/* Otherwise display the menu now; the timeout has already been
cancelled, since the user pressed a key. */
hiddenmenu = 0;
key_timeout = 0;
}
/* Handle both local and global timeout */
if (setjmp(timeout_jump)) {
entry = cm->defentry;
if (top < 0 || top < entry - MENU_ROWS + 1)
top = max(0, entry - MENU_ROWS + 1);
else if (top > entry || top > max(0, cm->nentries - MENU_ROWS))
top = min(entry, max(0, cm->nentries - MENU_ROWS));
draw_menu(cm->ontimeout ? -1 : entry, top, 1);
cmdline =
cm->ontimeout ? cm->ontimeout : cm->menu_entries[entry]->cmdline;
done = 1;
}
while (!done) {
if (entry <= 0) {
entry = 0;
while (entry < cm->nentries && is_disabled(cm->menu_entries[entry]))
entry++;
}
if (entry >= cm->nentries - 1) {
entry = cm->nentries - 1;
while (entry > 0 && is_disabled(cm->menu_entries[entry]))
entry--;
}
me = cm->menu_entries[entry];
if (top < 0 || top < entry - MENU_ROWS + 1)
top = max(0, entry - MENU_ROWS + 1);
else if (top > entry || top > max(0, cm->nentries - MENU_ROWS))
top = min(entry, max(0, cm->nentries - MENU_ROWS));
/* Start with a clear screen */
if (clear) {
/* Clear and redraw whole screen */
/* Enable ASCII on G0 and DEC VT on G1; do it in this order
to avoid confusing the Linux console */
if (clear >= 2)
prepare_screen_for_menu();
clear_screen();
clear = 0;
prev_entry = prev_top = -1;
}
if (top != prev_top) {
draw_menu(entry, top, 1);
display_help(me->helptext);
} else if (entry != prev_entry) {
draw_row(prev_entry - top + 4 + VSHIFT, entry, top, 0, 0);
draw_row(entry - top + 4 + VSHIFT, entry, top, 0, 0);
display_help(me->helptext);
}
prev_entry = entry;
prev_top = top;
cm->curentry = entry;
cm->curtop = top;
/* Cursor movement cancels timeout */
if (entry != cm->defentry)
key_timeout = 0;
if (key_timeout) {
int tol = timeout_left / CLK_TCK;
print_timeout_message(tol, TIMEOUT_ROW, cm->messages[MSG_AUTOBOOT]);
to_clear = 1;
} else {
to_clear = 0;
}
if (hotkey && me->immediate) {
/* If the hotkey was flagged immediate, simulate pressing ENTER */
key = KEY_ENTER;
} else {
this_timeout = min(min(key_timeout, timeout_left),
(clock_t) CLK_TCK);
key = mygetkey(this_timeout);
if (key != KEY_NONE) {
timeout_left = key_timeout;
if (to_clear)
printf("\033[%d;1H\1#0\033[K", TIMEOUT_ROW);
}
}
hotkey = false;
switch (key) {
case KEY_NONE: /* Timeout */
/* This is somewhat hacky, but this at least lets the user
know what's going on, and still deals with "phantom inputs"
e.g. on serial ports.
Warning: a timeout will boot the default entry without any
password! */
if (key_timeout) {
if (timeout_left <= this_timeout)
longjmp(timeout_jump, 1);
timeout_left -= this_timeout;
}
break;
case KEY_CTRL('L'):
clear = 1;
break;
case KEY_ENTER:
case KEY_CTRL('J'):
key_timeout = 0; /* Cancels timeout */
if (me->passwd) {
clear = 1;
done = ask_passwd(me->passwd);
} else {
done = 1;
}
cmdline = NULL;
if (done) {
switch (me->action) {
case MA_CMD:
cmdline = me->cmdline;
break;
case MA_SUBMENU:
case MA_GOTO:
case MA_EXIT:
done = 0;
clear = 2;
cm = me->submenu;
entry = cm->curentry;
top = cm->curtop;
break;
case MA_QUIT:
/* Quit menu system */
done = 1;
clear = 1;
draw_row(entry - top + 4 + VSHIFT, -1, top, 0, 0);
break;
case MA_HELP:
key = show_message_file(me->cmdline, me->background);
/* If the exit was an F-key, display that help screen */
show_fkey(key);
done = 0;
clear = 1;
break;
default:
done = 0;
break;
}
}
if (done && !me->passwd) {
/* Only save a new default if we don't have a password... */
if (me->save && me->label) {
syslinux_setadv(ADV_MENUSAVE, strlen(me->label), me->label);
syslinux_adv_write();
}
}
break;
case KEY_UP:
case KEY_CTRL('P'):
while (entry > 0) {
entry--;
if (entry < top)
top -= MENU_ROWS;
if (!is_disabled(cm->menu_entries[entry]))
break;
}
break;
case KEY_DOWN:
case KEY_CTRL('N'):
while (entry < cm->nentries - 1) {
entry++;
if (entry >= top + MENU_ROWS)
top += MENU_ROWS;
if (!is_disabled(cm->menu_entries[entry]))
break;
}
break;
case KEY_PGUP:
case KEY_LEFT:
case KEY_CTRL('B'):
case '<':
entry -= MENU_ROWS;
top -= MENU_ROWS;
while (entry > 0 && is_disabled(cm->menu_entries[entry])) {
entry--;
if (entry < top)
top -= MENU_ROWS;
}
break;
case KEY_PGDN:
case KEY_RIGHT:
case KEY_CTRL('F'):
case '>':
case ' ':
entry += MENU_ROWS;
top += MENU_ROWS;
while (entry < cm->nentries - 1
&& is_disabled(cm->menu_entries[entry])) {
entry++;
if (entry >= top + MENU_ROWS)
top += MENU_ROWS;
}
break;
case '-':
while (entry > 0) {
entry--;
top--;
if (!is_disabled(cm->menu_entries[entry]))
break;
}
break;
case '+':
while (entry < cm->nentries - 1) {
entry++;
top++;
if (!is_disabled(cm->menu_entries[entry]))
break;
}
break;
case KEY_CTRL('A'):
case KEY_HOME:
top = entry = 0;
break;
case KEY_CTRL('E'):
case KEY_END:
entry = cm->nentries - 1;
top = max(0, cm->nentries - MENU_ROWS);
break;
case KEY_F1:
case KEY_F2:
case KEY_F3:
case KEY_F4:
case KEY_F5:
case KEY_F6:
case KEY_F7:
case KEY_F8:
case KEY_F9:
case KEY_F10:
case KEY_F11:
case KEY_F12:
show_fkey(key);
clear = 1;
break;
case KEY_TAB:
if (cm->allowedit && me->action == MA_CMD) {
int ok = 1;
key_timeout = 0; /* Cancels timeout */
draw_row(entry - top + 4 + VSHIFT, -1, top, 0, 0);
if (cm->menu_master_passwd) {
ok = ask_passwd(NULL);
clear_screen();
draw_menu(-1, top, 0);
} else {
/* Erase [Tab] message and help text */
printf("\033[%d;1H\1#0\033[K", TABMSG_ROW);
display_help(NULL);
}
if (ok) {
cmdline = edit_cmdline(me->cmdline, top);
done = !!cmdline;
clear = 1; /* In case we hit [Esc] and done is null */
} else {
draw_row(entry - top + 4 + VSHIFT, entry, top, 0, 0);
}
}
break;
case KEY_CTRL('C'): /* Ctrl-C */
case KEY_ESC: /* Esc */
if (cm->parent) {
cm = cm->parent;
clear = 2;
entry = cm->curentry;
top = cm->curtop;
} else if (cm->allowedit) {
done = 1;
clear = 1;
key_timeout = 0;
draw_row(entry - top + 4 + VSHIFT, -1, top, 0, 0);
if (cm->menu_master_passwd)
done = ask_passwd(NULL);
}
break;
default:
if (key > 0 && key < 0xFF) {
key &= ~0x20; /* Upper case */
if (cm->menu_hotkeys[key]) {
key_timeout = 0;
entry = cm->menu_hotkeys[key]->entry;
/* Should we commit at this point? */
hotkey = true;
}
}
break;
}
}
printf("\033[?25h"); /* Show cursor */
/* Return the label name so localboot and ipappend work */
return cmdline;
}
int main(int argc, char *argv[])
{
const char *cmdline;
struct menu *m;
int rows, cols;
int i;
(void)argc;
parse_configs(argv + 1);
/*
* We don't start the console until we have parsed the configuration
* file, since the configuration file might impact the console
* configuration, e.g. MENU RESOLUTION.
*/
start_console();
if (getscreensize(1, &rows, &cols)) {
/* Unknown screen size? */
rows = 24;
cols = 80;
}
/* Some postprocessing for all menus */
for (m = menu_list; m; m = m->next) {
if (!m->mparm[P_WIDTH])
m->mparm[P_WIDTH] = cols;
/* If anyone has specified negative parameters, consider them
relative to the bottom row of the screen. */
for (i = 0; i < NPARAMS; i++)
if (m->mparm[i] < 0)
m->mparm[i] = max(m->mparm[i] + rows, 0);
}
cm = start_menu;
if (!cm->nentries) {
fputs("Initial menu has no LABEL entries!\n", stdout);
return 1; /* Error! */
}
for (;;) {
local_cursor_enable(true);
cmdline = run_menu();
if (clearmenu)
clear_screen();
local_cursor_enable(false);
printf("\033[?25h\033[%d;1H\033[0m", END_ROW);
if (cmdline) {
uint32_t type = parse_image_type(cmdline);
execute(cmdline, type, false);
if (cm->onerror) {
type = parse_image_type(cm->onerror);
execute(cm->onerror, type, true);
}
} else {
return 0; /* Exit */
}
}
}