Home | History | Annotate | Download | only in menu
      1 /* ----------------------------------------------------------------------- *
      2  *
      3  *   Copyright 2004-2008 H. Peter Anvin - All Rights Reserved
      4  *   Copyright 2009-2014 Intel Corporation; author: H. Peter Anvin
      5  *
      6  *   This program is free software; you can redistribute it and/or modify
      7  *   it under the terms of the GNU General Public License as published by
      8  *   the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
      9  *   Boston MA 02110-1301, USA; either version 2 of the License, or
     10  *   (at your option) any later version; incorporated herein by reference.
     11  *
     12  * ----------------------------------------------------------------------- */
     13 
     14 /*
     15  * menumain.c
     16  *
     17  * Simple menu system which displays a list and allows the user to select
     18  * a command line and/or edit it.
     19  */
     20 
     21 #include <ctype.h>
     22 #include <string.h>
     23 #include <stdlib.h>
     24 #include <stdio.h>
     25 #include <consoles.h>
     26 #include <getkey.h>
     27 #include <minmax.h>
     28 #include <setjmp.h>
     29 #include <limits.h>
     30 #include <com32.h>
     31 #include <core.h>
     32 #include <syslinux/adv.h>
     33 #include <syslinux/boot.h>
     34 
     35 #include "menu.h"
     36 
     37 /* The symbol "cm" always refers to the current menu across this file... */
     38 static struct menu *cm;
     39 
     40 const struct menu_parameter mparm[NPARAMS] = {
     41     [P_WIDTH] = {"width", 0},
     42     [P_MARGIN] = {"margin", 10},
     43     [P_PASSWD_MARGIN] = {"passwordmargin", 3},
     44     [P_MENU_ROWS] = {"rows", 12},
     45     [P_TABMSG_ROW] = {"tabmsgrow", 18},
     46     [P_CMDLINE_ROW] = {"cmdlinerow", 18},
     47     [P_END_ROW] = {"endrow", -1},
     48     [P_PASSWD_ROW] = {"passwordrow", 11},
     49     [P_TIMEOUT_ROW] = {"timeoutrow", 20},
     50     [P_HELPMSG_ROW] = {"helpmsgrow", 22},
     51     [P_HELPMSGEND_ROW] = {"helpmsgendrow", -1},
     52     [P_HSHIFT] = {"hshift", 0},
     53     [P_VSHIFT] = {"vshift", 0},
     54     [P_HIDDEN_ROW] = {"hiddenrow", -2},
     55 };
     56 
     57 /* These macros assume "cm" is a pointer to the current menu */
     58 #define WIDTH		(cm->mparm[P_WIDTH])
     59 #define MARGIN		(cm->mparm[P_MARGIN])
     60 #define PASSWD_MARGIN	(cm->mparm[P_PASSWD_MARGIN])
     61 #define MENU_ROWS	(cm->mparm[P_MENU_ROWS])
     62 #define TABMSG_ROW	(cm->mparm[P_TABMSG_ROW]+VSHIFT)
     63 #define CMDLINE_ROW	(cm->mparm[P_CMDLINE_ROW]+VSHIFT)
     64 #define END_ROW		(cm->mparm[P_END_ROW])
     65 #define PASSWD_ROW	(cm->mparm[P_PASSWD_ROW]+VSHIFT)
     66 #define TIMEOUT_ROW	(cm->mparm[P_TIMEOUT_ROW]+VSHIFT)
     67 #define HELPMSG_ROW	(cm->mparm[P_HELPMSG_ROW]+VSHIFT)
     68 #define HELPMSGEND_ROW	(cm->mparm[P_HELPMSGEND_ROW])
     69 #define HSHIFT		(cm->mparm[P_HSHIFT])
     70 #define VSHIFT		(cm->mparm[P_VSHIFT])
     71 #define HIDDEN_ROW	(cm->mparm[P_HIDDEN_ROW])
     72 
     73 static char *pad_line(const char *text, int align, int width)
     74 {
     75     static char buffer[MAX_CMDLINE_LEN];
     76     int n, p;
     77 
     78     if (width >= (int)sizeof buffer)
     79 	return NULL;		/* Can't do it */
     80 
     81     n = strlen(text);
     82     if (n >= width)
     83 	n = width;
     84 
     85     memset(buffer, ' ', width);
     86     buffer[width] = 0;
     87     p = ((width - n) * align) >> 1;
     88     memcpy(buffer + p, text, n);
     89 
     90     return buffer;
     91 }
     92 
     93 /* Display an entry, with possible hotkey highlight.  Assumes
     94    that the current attribute is the non-hotkey one, and will
     95    guarantee that as an exit condition as well. */
     96 static void
     97 display_entry(const struct menu_entry *entry, const char *attrib,
     98 	      const char *hotattrib, int width)
     99 {
    100     const char *p = entry->displayname;
    101     char marker;
    102 
    103     if (!p)
    104 	p = "";
    105 
    106     switch (entry->action) {
    107     case MA_SUBMENU:
    108 	marker = '>';
    109 	break;
    110     case MA_EXIT:
    111 	marker = '<';
    112 	break;
    113     default:
    114 	marker = 0;
    115 	break;
    116     }
    117 
    118     if (marker)
    119 	width -= 2;
    120 
    121     while (width) {
    122 	if (*p) {
    123 	    if (*p == '^') {
    124 		p++;
    125 		if (*p && ((unsigned char)*p & ~0x20) == entry->hotkey) {
    126 		    fputs(hotattrib, stdout);
    127 		    putchar(*p++);
    128 		    fputs(attrib, stdout);
    129 		    width--;
    130 		}
    131 	    } else {
    132 		putchar(*p++);
    133 		width--;
    134 	    }
    135 	} else {
    136 	    putchar(' ');
    137 	    width--;
    138 	}
    139     }
    140 
    141     if (marker) {
    142 	putchar(' ');
    143 	putchar(marker);
    144     }
    145 }
    146 
    147 static void draw_row(int y, int sel, int top, int sbtop, int sbbot)
    148 {
    149     int i = (y - 4 - VSHIFT) + top;
    150     int dis = (i < cm->nentries) && is_disabled(cm->menu_entries[i]);
    151 
    152     printf("\033[%d;%dH\1#1\016x\017%s ",
    153 	   y, MARGIN + 1 + HSHIFT,
    154 	   (i == sel) ? "\1#5" : dis ? "\2#17" : "\1#3");
    155 
    156     if (i >= cm->nentries) {
    157 	fputs(pad_line("", 0, WIDTH - 2 * MARGIN - 4), stdout);
    158     } else {
    159 	display_entry(cm->menu_entries[i],
    160 		      (i == sel) ? "\1#5" : dis ? "\2#17" : "\1#3",
    161 		      (i == sel) ? "\1#6" : dis ? "\2#17" : "\1#4",
    162 		      WIDTH - 2 * MARGIN - 4);
    163     }
    164 
    165     if (cm->nentries <= MENU_ROWS) {
    166 	printf(" \1#1\016x\017");
    167     } else if (sbtop > 0) {
    168 	if (y >= sbtop && y <= sbbot)
    169 	    printf(" \1#7\016a\017");
    170 	else
    171 	    printf(" \1#1\016x\017");
    172     } else {
    173 	putchar(' ');		/* Don't modify the scrollbar */
    174     }
    175 }
    176 
    177 static jmp_buf timeout_jump;
    178 
    179 int mygetkey(clock_t timeout)
    180 {
    181     clock_t t0, t;
    182     clock_t tto, to;
    183     int key;
    184 
    185     if (!totaltimeout)
    186 	return get_key(stdin, timeout);
    187 
    188     for (;;) {
    189 	tto = min(totaltimeout, INT_MAX);
    190 	to = timeout ? min(tto, timeout) : tto;
    191 
    192 	t0 = times(NULL);
    193 	key = get_key(stdin, to);
    194 	t = times(NULL) - t0;
    195 
    196 	if (totaltimeout <= t)
    197 	    longjmp(timeout_jump, 1);
    198 
    199 	totaltimeout -= t;
    200 
    201 	if (key != KEY_NONE)
    202 	    return key;
    203 
    204 	if (timeout) {
    205 	    if (timeout <= t)
    206 		return KEY_NONE;
    207 
    208 	    timeout -= t;
    209 	}
    210     }
    211 }
    212 
    213 static int ask_passwd(const char *menu_entry)
    214 {
    215     char user_passwd[WIDTH], *p;
    216     int done;
    217     int key;
    218     int x;
    219     int rv;
    220 
    221     printf("\033[%d;%dH\2#11\016l", PASSWD_ROW, PASSWD_MARGIN + 1);
    222     for (x = 2; x <= WIDTH - 2 * PASSWD_MARGIN - 1; x++)
    223 	putchar('q');
    224 
    225     printf("k\033[%d;%dHx", PASSWD_ROW + 1, PASSWD_MARGIN + 1);
    226     for (x = 2; x <= WIDTH - 2 * PASSWD_MARGIN - 1; x++)
    227 	putchar(' ');
    228 
    229     printf("x\033[%d;%dHm", PASSWD_ROW + 2, PASSWD_MARGIN + 1);
    230     for (x = 2; x <= WIDTH - 2 * PASSWD_MARGIN - 1; x++)
    231 	putchar('q');
    232 
    233     printf("j\017\033[%d;%dH\2#12 %s \033[%d;%dH\2#13",
    234 	   PASSWD_ROW, (WIDTH - (strlen(cm->messages[MSG_PASSPROMPT]) + 2)) / 2,
    235 	   cm->messages[MSG_PASSPROMPT], PASSWD_ROW + 1, PASSWD_MARGIN + 3);
    236 
    237     drain_keyboard();
    238 
    239     /* Actually allow user to type a password, then compare to the SHA1 */
    240     done = 0;
    241     p = user_passwd;
    242 
    243     while (!done) {
    244 	key = mygetkey(0);
    245 
    246 	switch (key) {
    247 	case KEY_ENTER:
    248 	case KEY_CTRL('J'):
    249 	    done = 1;
    250 	    break;
    251 
    252 	case KEY_ESC:
    253 	case KEY_CTRL('C'):
    254 	    p = user_passwd;	/* No password entered */
    255 	    done = 1;
    256 	    break;
    257 
    258 	case KEY_BACKSPACE:
    259 	case KEY_DEL:
    260 	case KEY_DELETE:
    261 	    if (p > user_passwd) {
    262 		printf("\b \b");
    263 		p--;
    264 	    }
    265 	    break;
    266 
    267 	case KEY_CTRL('U'):
    268 	    while (p > user_passwd) {
    269 		printf("\b \b");
    270 		p--;
    271 	    }
    272 	    break;
    273 
    274 	default:
    275 	    if (key >= ' ' && key <= 0xFF &&
    276 		(p - user_passwd) < WIDTH - 2 * PASSWD_MARGIN - 5) {
    277 		*p++ = key;
    278 		putchar('*');
    279 	    }
    280 	    break;
    281 	}
    282     }
    283 
    284     if (p == user_passwd)
    285 	return 0;		/* No password entered */
    286 
    287     *p = '\0';
    288 
    289     rv = (cm->menu_master_passwd &&
    290 	  passwd_compare(cm->menu_master_passwd, user_passwd))
    291 	|| (menu_entry && passwd_compare(menu_entry, user_passwd));
    292 
    293     /* Clean up */
    294     memset(user_passwd, 0, WIDTH);
    295     drain_keyboard();
    296 
    297     return rv;
    298 }
    299 
    300 static void draw_menu(int sel, int top, int edit_line)
    301 {
    302     int x, y;
    303     int sbtop = 0, sbbot = 0;
    304     const char *tabmsg;
    305     int tabmsg_len;
    306 
    307     if (cm->nentries > MENU_ROWS) {
    308 	int sblen = max(MENU_ROWS * MENU_ROWS / cm->nentries, 1);
    309 	sbtop = (MENU_ROWS - sblen + 1) * top / (cm->nentries - MENU_ROWS + 1);
    310 	sbbot = sbtop + sblen - 1;
    311 	sbtop += 4;
    312 	sbbot += 4;		/* Starting row of scrollbar */
    313     }
    314 
    315     printf("\033[%d;%dH\1#1\016l", VSHIFT + 1, HSHIFT + MARGIN + 1);
    316     for (x = 2 + HSHIFT; x <= (WIDTH - 2 * MARGIN - 1) + HSHIFT; x++)
    317 	putchar('q');
    318 
    319     printf("k\033[%d;%dH\1#1x\017\1#2 %s \1#1\016x",
    320 	   VSHIFT + 2,
    321 	   HSHIFT + MARGIN + 1, pad_line(cm->title, 1, WIDTH - 2 * MARGIN - 4));
    322 
    323     printf("\033[%d;%dH\1#1t", VSHIFT + 3, HSHIFT + MARGIN + 1);
    324     for (x = 2 + HSHIFT; x <= (WIDTH - 2 * MARGIN - 1) + HSHIFT; x++)
    325 	putchar('q');
    326     fputs("u\017", stdout);
    327 
    328     for (y = 4 + VSHIFT; y < 4 + VSHIFT + MENU_ROWS; y++)
    329 	draw_row(y, sel, top, sbtop, sbbot);
    330 
    331     printf("\033[%d;%dH\1#1\016m", y, HSHIFT + MARGIN + 1);
    332     for (x = 2 + HSHIFT; x <= (WIDTH - 2 * MARGIN - 1) + HSHIFT; x++)
    333 	putchar('q');
    334     fputs("j\017", stdout);
    335 
    336     if (edit_line && cm->allowedit && !cm->menu_master_passwd)
    337 	tabmsg = cm->messages[MSG_TAB];
    338     else
    339 	tabmsg = cm->messages[MSG_NOTAB];
    340 
    341     tabmsg_len = strlen(tabmsg);
    342 
    343     printf("\1#8\033[%d;%dH%s",
    344 	   TABMSG_ROW, 1 + HSHIFT + ((WIDTH - tabmsg_len) >> 1), tabmsg);
    345     printf("\1#0\033[%d;1H", END_ROW);
    346 }
    347 
    348 static void clear_screen(void)
    349 {
    350     fputs("\033e\033%@\033)0\033(B\1#0\033[?25l\033[2J", stdout);
    351 }
    352 
    353 static void display_help(const char *text)
    354 {
    355     int row;
    356     const char *p;
    357 
    358     if (!text) {
    359 	text = "";
    360 	printf("\1#0\033[%d;1H", HELPMSG_ROW);
    361     } else {
    362 	printf("\2#16\033[%d;1H", HELPMSG_ROW);
    363     }
    364 
    365     for (p = text, row = HELPMSG_ROW; *p && row <= HELPMSGEND_ROW; p++) {
    366 	switch (*p) {
    367 	case '\r':
    368 	case '\f':
    369 	case '\v':
    370 	case '\033':
    371 	    break;
    372 	case '\n':
    373 	    printf("\033[K\033[%d;1H", ++row);
    374 	    break;
    375 	default:
    376 	    putchar(*p);
    377 	}
    378     }
    379 
    380     fputs("\033[K", stdout);
    381 
    382     while (row <= HELPMSGEND_ROW) {
    383 	printf("\033[K\033[%d;1H", ++row);
    384     }
    385 }
    386 
    387 static void show_fkey(int key)
    388 {
    389     int fkey;
    390 
    391     while (1) {
    392 	switch (key) {
    393 	case KEY_F1:
    394 	    fkey = 0;
    395 	    break;
    396 	case KEY_F2:
    397 	    fkey = 1;
    398 	    break;
    399 	case KEY_F3:
    400 	    fkey = 2;
    401 	    break;
    402 	case KEY_F4:
    403 	    fkey = 3;
    404 	    break;
    405 	case KEY_F5:
    406 	    fkey = 4;
    407 	    break;
    408 	case KEY_F6:
    409 	    fkey = 5;
    410 	    break;
    411 	case KEY_F7:
    412 	    fkey = 6;
    413 	    break;
    414 	case KEY_F8:
    415 	    fkey = 7;
    416 	    break;
    417 	case KEY_F9:
    418 	    fkey = 8;
    419 	    break;
    420 	case KEY_F10:
    421 	    fkey = 9;
    422 	    break;
    423 	case KEY_F11:
    424 	    fkey = 10;
    425 	    break;
    426 	case KEY_F12:
    427 	    fkey = 11;
    428 	    break;
    429 	default:
    430 	    fkey = -1;
    431 	    break;
    432 	}
    433 
    434 	if (fkey == -1)
    435 	    break;
    436 
    437 	if (cm->fkeyhelp[fkey].textname)
    438 	    key = show_message_file(cm->fkeyhelp[fkey].textname,
    439 				    cm->fkeyhelp[fkey].background);
    440 	else
    441 	    break;
    442     }
    443 }
    444 
    445 static const char *edit_cmdline(const char *input, int top)
    446 {
    447     static char cmdline[MAX_CMDLINE_LEN];
    448     int key, len, prev_len, cursor;
    449     int redraw = 1;		/* We enter with the menu already drawn */
    450 
    451     strlcpy(cmdline, input, MAX_CMDLINE_LEN);
    452     cmdline[MAX_CMDLINE_LEN - 1] = '\0';
    453 
    454     len = cursor = strlen(cmdline);
    455     prev_len = 0;
    456 
    457     for (;;) {
    458 	if (redraw > 1) {
    459 	    /* Clear and redraw whole screen */
    460 	    /* Enable ASCII on G0 and DEC VT on G1; do it in this order
    461 	       to avoid confusing the Linux console */
    462 	    clear_screen();
    463 	    draw_menu(-1, top, 1);
    464 	    prev_len = 0;
    465 	}
    466 
    467 	if (redraw > 0) {
    468 	    /* Redraw the command line */
    469 	    printf("\033[?25l\033[%d;1H\1#9> \2#10%s",
    470 		   CMDLINE_ROW, pad_line(cmdline, 0, max(len, prev_len)));
    471 	    printf("\2#10\033[%d;3H%s\033[?25h",
    472 		   CMDLINE_ROW, pad_line(cmdline, 0, cursor));
    473 	    prev_len = len;
    474 	    redraw = 0;
    475 	}
    476 
    477 	key = mygetkey(0);
    478 
    479 	switch (key) {
    480 	case KEY_CTRL('L'):
    481 	    redraw = 2;
    482 	    break;
    483 
    484 	case KEY_ENTER:
    485 	case KEY_CTRL('J'):
    486 	    return cmdline;
    487 
    488 	case KEY_ESC:
    489 	case KEY_CTRL('C'):
    490 	    return NULL;
    491 
    492 	case KEY_BACKSPACE:
    493 	case KEY_DEL:
    494 	    if (cursor) {
    495 		memmove(cmdline + cursor - 1, cmdline + cursor,
    496 			len - cursor + 1);
    497 		len--;
    498 		cursor--;
    499 		redraw = 1;
    500 	    }
    501 	    break;
    502 
    503 	case KEY_CTRL('D'):
    504 	case KEY_DELETE:
    505 	    if (cursor < len) {
    506 		memmove(cmdline + cursor, cmdline + cursor + 1, len - cursor);
    507 		len--;
    508 		redraw = 1;
    509 	    }
    510 	    break;
    511 
    512 	case KEY_CTRL('U'):
    513 	    if (len) {
    514 		len = cursor = 0;
    515 		cmdline[len] = '\0';
    516 		redraw = 1;
    517 	    }
    518 	    break;
    519 
    520 	case KEY_CTRL('W'):
    521 	    if (cursor) {
    522 		int prevcursor = cursor;
    523 
    524 		while (cursor && my_isspace(cmdline[cursor - 1]))
    525 		    cursor--;
    526 
    527 		while (cursor && !my_isspace(cmdline[cursor - 1]))
    528 		    cursor--;
    529 
    530 		memmove(cmdline + cursor, cmdline + prevcursor,
    531 			len - prevcursor + 1);
    532 		len -= (prevcursor - cursor);
    533 		redraw = 1;
    534 	    }
    535 	    break;
    536 
    537 	case KEY_LEFT:
    538 	case KEY_CTRL('B'):
    539 	    if (cursor) {
    540 		cursor--;
    541 		redraw = 1;
    542 	    }
    543 	    break;
    544 
    545 	case KEY_RIGHT:
    546 	case KEY_CTRL('F'):
    547 	    if (cursor < len) {
    548 		putchar(cmdline[cursor++]);
    549 	    }
    550 	    break;
    551 
    552 	case KEY_CTRL('K'):
    553 	    if (cursor < len) {
    554 		cmdline[len = cursor] = '\0';
    555 		redraw = 1;
    556 	    }
    557 	    break;
    558 
    559 	case KEY_HOME:
    560 	case KEY_CTRL('A'):
    561 	    if (cursor) {
    562 		cursor = 0;
    563 		redraw = 1;
    564 	    }
    565 	    break;
    566 
    567 	case KEY_END:
    568 	case KEY_CTRL('E'):
    569 	    if (cursor != len) {
    570 		cursor = len;
    571 		redraw = 1;
    572 	    }
    573 	    break;
    574 
    575 	case KEY_F1:
    576 	case KEY_F2:
    577 	case KEY_F3:
    578 	case KEY_F4:
    579 	case KEY_F5:
    580 	case KEY_F6:
    581 	case KEY_F7:
    582 	case KEY_F8:
    583 	case KEY_F9:
    584 	case KEY_F10:
    585 	case KEY_F11:
    586 	case KEY_F12:
    587 	    show_fkey(key);
    588 	    redraw = 1;
    589 	    break;
    590 
    591 	default:
    592 	    if (key >= ' ' && key <= 0xFF && len < MAX_CMDLINE_LEN - 1) {
    593 		if (cursor == len) {
    594 		    cmdline[len] = key;
    595 		    cmdline[++len] = '\0';
    596 		    cursor++;
    597 		    putchar(key);
    598 		    prev_len++;
    599 		} else {
    600 		    memmove(cmdline + cursor + 1, cmdline + cursor,
    601 			    len - cursor + 1);
    602 		    cmdline[cursor++] = key;
    603 		    len++;
    604 		    redraw = 1;
    605 		}
    606 	    }
    607 	    break;
    608 	}
    609     }
    610 }
    611 
    612 static void print_timeout_message(int tol, int row, const char *msg)
    613 {
    614     static int last_msg_len = 0;
    615     char buf[256];
    616     int nc = 0, nnc, padc;
    617     const char *tp = msg;
    618     char tc;
    619     char *tq = buf;
    620 
    621     while ((size_t) (tq - buf) < (sizeof buf - 16) && (tc = *tp)) {
    622 	tp++;
    623 	if (tc == '#') {
    624 	    nnc = sprintf(tq, "\2#15%d\2#14", tol);
    625 	    tq += nnc;
    626 	    nc += nnc - 8;	/* 8 formatting characters */
    627 	} else if (tc == '{') {
    628 	    /* Deal with {singular[,dual],plural} constructs */
    629 	    struct {
    630 		const char *s, *e;
    631 	    } tx[3];
    632 	    const char *tpp;
    633 	    int n = 0;
    634 
    635 	    memset(tx, 0, sizeof tx);
    636 
    637 	    tx[0].s = tp;
    638 
    639 	    while (*tp && *tp != '}') {
    640 		if (*tp == ',' && n < 2) {
    641 		    tx[n].e = tp;
    642 		    n++;
    643 		    tx[n].s = tp + 1;
    644 		}
    645 		tp++;
    646 	    }
    647 	    tx[n].e = tp;
    648 
    649 	    if (*tp)
    650 		tp++;		/* Skip final bracket */
    651 
    652 	    if (!tx[1].s)
    653 		tx[1] = tx[0];
    654 	    if (!tx[2].s)
    655 		tx[2] = tx[1];
    656 
    657 	    /* Now [0] is singular, [1] is dual, and [2] is plural,
    658 	       even if the user only specified some of them. */
    659 
    660 	    switch (tol) {
    661 	    case 1:
    662 		n = 0;
    663 		break;
    664 	    case 2:
    665 		n = 1;
    666 		break;
    667 	    default:
    668 		n = 2;
    669 		break;
    670 	    }
    671 
    672 	    for (tpp = tx[n].s; tpp < tx[n].e; tpp++) {
    673 		if ((size_t) (tq - buf) < (sizeof buf)) {
    674 		    *tq++ = *tpp;
    675 		    nc++;
    676 		}
    677 	    }
    678 	} else {
    679 	    *tq++ = tc;
    680 	    nc++;
    681 	}
    682     }
    683     *tq = '\0';
    684 
    685     if (nc >= last_msg_len) {
    686 	padc = 0;
    687     } else {
    688 	padc = (last_msg_len - nc + 1) >> 1;
    689     }
    690 
    691     printf("\033[%d;%dH\2#14%*s%s%*s", row,
    692 	   HSHIFT + 1 + ((WIDTH - nc) >> 1) - padc,
    693 	   padc, "", buf, padc, "");
    694 
    695     last_msg_len = nc;
    696 }
    697 
    698 /* Set the background screen, etc. */
    699 static void prepare_screen_for_menu(void)
    700 {
    701     console_color_table = cm->color_table;
    702     console_color_table_size = menu_color_table_size;
    703     set_background(cm->menu_background);
    704 }
    705 
    706 static const char *do_hidden_menu(void)
    707 {
    708     int key;
    709     int timeout_left, this_timeout;
    710 
    711     clear_screen();
    712 
    713     if (!setjmp(timeout_jump)) {
    714 	timeout_left = cm->timeout;
    715 
    716 	while (!cm->timeout || timeout_left) {
    717 	    int tol = timeout_left / CLK_TCK;
    718 
    719 	    print_timeout_message(tol, HIDDEN_ROW, cm->messages[MSG_AUTOBOOT]);
    720 
    721 	    this_timeout = min(timeout_left, CLK_TCK);
    722 	    key = mygetkey(this_timeout);
    723 
    724 	    if (key != KEY_NONE) {
    725 		/* Clear the message from the screen */
    726 		print_timeout_message(0, HIDDEN_ROW, "");
    727 		return hide_key[key]; /* NULL if no MENU HIDEKEY in effect */
    728 	    }
    729 
    730 	    timeout_left -= this_timeout;
    731 	}
    732     }
    733 
    734     /* Clear the message from the screen */
    735     print_timeout_message(0, HIDDEN_ROW, "");
    736 
    737     if (cm->ontimeout)
    738 	return cm->ontimeout;
    739     else
    740 	return cm->menu_entries[cm->defentry]->cmdline; /* Default entry */
    741 }
    742 
    743 static const char *run_menu(void)
    744 {
    745     int key;
    746     int done = 0;
    747     volatile int entry = cm->curentry;
    748     int prev_entry = -1;
    749     volatile int top = cm->curtop;
    750     int prev_top = -1;
    751     int clear = 1, to_clear;
    752     const char *cmdline = NULL;
    753     volatile clock_t key_timeout, timeout_left, this_timeout;
    754     const struct menu_entry *me;
    755     bool hotkey = false;
    756 
    757     /* Note: for both key_timeout and timeout == 0 means no limit */
    758     timeout_left = key_timeout = cm->timeout;
    759 
    760     /* If we're in shiftkey mode, exit immediately unless a shift key
    761        is pressed */
    762     if (shiftkey && !shift_is_held()) {
    763 	return cm->menu_entries[cm->defentry]->cmdline;
    764     } else {
    765 	shiftkey = 0;
    766     }
    767 
    768     /* Do this before hiddenmenu handling, so we show the background */
    769     prepare_screen_for_menu();
    770 
    771     /* Handle hiddenmenu */
    772     if (hiddenmenu) {
    773 	cmdline = do_hidden_menu();
    774 	if (cmdline)
    775 	    return cmdline;
    776 
    777 	/* Otherwise display the menu now; the timeout has already been
    778 	   cancelled, since the user pressed a key. */
    779 	hiddenmenu = 0;
    780 	key_timeout = 0;
    781     }
    782 
    783     /* Handle both local and global timeout */
    784     if (setjmp(timeout_jump)) {
    785 	entry = cm->defentry;
    786 
    787 	if (top < 0 || top < entry - MENU_ROWS + 1)
    788 	    top = max(0, entry - MENU_ROWS + 1);
    789 	else if (top > entry || top > max(0, cm->nentries - MENU_ROWS))
    790 	    top = min(entry, max(0, cm->nentries - MENU_ROWS));
    791 
    792 	draw_menu(cm->ontimeout ? -1 : entry, top, 1);
    793 	cmdline =
    794 	    cm->ontimeout ? cm->ontimeout : cm->menu_entries[entry]->cmdline;
    795 	done = 1;
    796     }
    797 
    798     while (!done) {
    799 	if (entry <= 0) {
    800 	    entry = 0;
    801 	    while (entry < cm->nentries && is_disabled(cm->menu_entries[entry]))
    802 		entry++;
    803 	}
    804 	if (entry >= cm->nentries - 1) {
    805 	    entry = cm->nentries - 1;
    806 	    while (entry > 0 && is_disabled(cm->menu_entries[entry]))
    807 		entry--;
    808 	}
    809 
    810 	me = cm->menu_entries[entry];
    811 
    812 	if (top < 0 || top < entry - MENU_ROWS + 1)
    813 	    top = max(0, entry - MENU_ROWS + 1);
    814 	else if (top > entry || top > max(0, cm->nentries - MENU_ROWS))
    815 	    top = min(entry, max(0, cm->nentries - MENU_ROWS));
    816 
    817 	/* Start with a clear screen */
    818 	if (clear) {
    819 	    /* Clear and redraw whole screen */
    820 	    /* Enable ASCII on G0 and DEC VT on G1; do it in this order
    821 	       to avoid confusing the Linux console */
    822 	    if (clear >= 2)
    823 		prepare_screen_for_menu();
    824 	    clear_screen();
    825 	    clear = 0;
    826 	    prev_entry = prev_top = -1;
    827 	}
    828 
    829 	if (top != prev_top) {
    830 	    draw_menu(entry, top, 1);
    831 	    display_help(me->helptext);
    832 	} else if (entry != prev_entry) {
    833 	    draw_row(prev_entry - top + 4 + VSHIFT, entry, top, 0, 0);
    834 	    draw_row(entry - top + 4 + VSHIFT, entry, top, 0, 0);
    835 	    display_help(me->helptext);
    836 	}
    837 
    838 	prev_entry = entry;
    839 	prev_top = top;
    840 	cm->curentry = entry;
    841 	cm->curtop = top;
    842 
    843 	/* Cursor movement cancels timeout */
    844 	if (entry != cm->defentry)
    845 	    key_timeout = 0;
    846 
    847 	if (key_timeout) {
    848 	    int tol = timeout_left / CLK_TCK;
    849 	    print_timeout_message(tol, TIMEOUT_ROW, cm->messages[MSG_AUTOBOOT]);
    850 	    to_clear = 1;
    851 	} else {
    852 	    to_clear = 0;
    853 	}
    854 
    855 	if (hotkey && me->immediate) {
    856 	    /* If the hotkey was flagged immediate, simulate pressing ENTER */
    857 	    key = KEY_ENTER;
    858 	} else {
    859 	    this_timeout = min(min(key_timeout, timeout_left),
    860 			       (clock_t) CLK_TCK);
    861 	    key = mygetkey(this_timeout);
    862 
    863 	    if (key != KEY_NONE) {
    864 		timeout_left = key_timeout;
    865 		if (to_clear)
    866 		    printf("\033[%d;1H\1#0\033[K", TIMEOUT_ROW);
    867 	    }
    868 	}
    869 
    870 	hotkey = false;
    871 
    872 	switch (key) {
    873 	case KEY_NONE:		/* Timeout */
    874 	    /* This is somewhat hacky, but this at least lets the user
    875 	       know what's going on, and still deals with "phantom inputs"
    876 	       e.g. on serial ports.
    877 
    878 	       Warning: a timeout will boot the default entry without any
    879 	       password! */
    880 	    if (key_timeout) {
    881 		if (timeout_left <= this_timeout)
    882 		    longjmp(timeout_jump, 1);
    883 
    884 		timeout_left -= this_timeout;
    885 	    }
    886 	    break;
    887 
    888 	case KEY_CTRL('L'):
    889 	    clear = 1;
    890 	    break;
    891 
    892 	case KEY_ENTER:
    893 	case KEY_CTRL('J'):
    894 	    key_timeout = 0;	/* Cancels timeout */
    895 	    if (me->passwd) {
    896 		clear = 1;
    897 		done = ask_passwd(me->passwd);
    898 	    } else {
    899 		done = 1;
    900 	    }
    901 	    cmdline = NULL;
    902 	    if (done) {
    903 		switch (me->action) {
    904 		case MA_CMD:
    905 		    cmdline = me->cmdline;
    906 		    break;
    907 		case MA_SUBMENU:
    908 		case MA_GOTO:
    909 		case MA_EXIT:
    910 		    done = 0;
    911 		    clear = 2;
    912 		    cm = me->submenu;
    913 		    entry = cm->curentry;
    914 		    top = cm->curtop;
    915 		    break;
    916 		case MA_QUIT:
    917 		    /* Quit menu system */
    918 		    done = 1;
    919 		    clear = 1;
    920 		    draw_row(entry - top + 4 + VSHIFT, -1, top, 0, 0);
    921 		    break;
    922 		case MA_HELP:
    923 		    key = show_message_file(me->cmdline, me->background);
    924 		    /* If the exit was an F-key, display that help screen */
    925 		    show_fkey(key);
    926 		    done = 0;
    927 		    clear = 1;
    928 		    break;
    929 		default:
    930 		    done = 0;
    931 		    break;
    932 		}
    933 	    }
    934 	    if (done && !me->passwd) {
    935 		/* Only save a new default if we don't have a password... */
    936 		if (me->save && me->label) {
    937 		    syslinux_setadv(ADV_MENUSAVE, strlen(me->label), me->label);
    938 		    syslinux_adv_write();
    939 		}
    940 	    }
    941 	    break;
    942 
    943 	case KEY_UP:
    944 	case KEY_CTRL('P'):
    945 	    while (entry > 0) {
    946 		entry--;
    947 		if (entry < top)
    948 		    top -= MENU_ROWS;
    949 		if (!is_disabled(cm->menu_entries[entry]))
    950 		    break;
    951 	    }
    952 	    break;
    953 
    954 	case KEY_DOWN:
    955 	case KEY_CTRL('N'):
    956 	    while (entry < cm->nentries - 1) {
    957 		entry++;
    958 		if (entry >= top + MENU_ROWS)
    959 		    top += MENU_ROWS;
    960 		if (!is_disabled(cm->menu_entries[entry]))
    961 		    break;
    962 	    }
    963 	    break;
    964 
    965 	case KEY_PGUP:
    966 	case KEY_LEFT:
    967 	case KEY_CTRL('B'):
    968 	case '<':
    969 	    entry -= MENU_ROWS;
    970 	    top -= MENU_ROWS;
    971 	    while (entry > 0 && is_disabled(cm->menu_entries[entry])) {
    972 		entry--;
    973 		if (entry < top)
    974 		    top -= MENU_ROWS;
    975 	    }
    976 	    break;
    977 
    978 	case KEY_PGDN:
    979 	case KEY_RIGHT:
    980 	case KEY_CTRL('F'):
    981 	case '>':
    982 	case ' ':
    983 	    entry += MENU_ROWS;
    984 	    top += MENU_ROWS;
    985 	    while (entry < cm->nentries - 1
    986 		   && is_disabled(cm->menu_entries[entry])) {
    987 		entry++;
    988 		if (entry >= top + MENU_ROWS)
    989 		    top += MENU_ROWS;
    990 	    }
    991 	    break;
    992 
    993 	case '-':
    994 	    while (entry > 0) {
    995 		entry--;
    996 		top--;
    997 		if (!is_disabled(cm->menu_entries[entry]))
    998 		    break;
    999 	    }
   1000 	    break;
   1001 
   1002 	case '+':
   1003 	    while (entry < cm->nentries - 1) {
   1004 		entry++;
   1005 		top++;
   1006 		if (!is_disabled(cm->menu_entries[entry]))
   1007 		    break;
   1008 	    }
   1009 	    break;
   1010 
   1011 	case KEY_CTRL('A'):
   1012 	case KEY_HOME:
   1013 	    top = entry = 0;
   1014 	    break;
   1015 
   1016 	case KEY_CTRL('E'):
   1017 	case KEY_END:
   1018 	    entry = cm->nentries - 1;
   1019 	    top = max(0, cm->nentries - MENU_ROWS);
   1020 	    break;
   1021 
   1022 	case KEY_F1:
   1023 	case KEY_F2:
   1024 	case KEY_F3:
   1025 	case KEY_F4:
   1026 	case KEY_F5:
   1027 	case KEY_F6:
   1028 	case KEY_F7:
   1029 	case KEY_F8:
   1030 	case KEY_F9:
   1031 	case KEY_F10:
   1032 	case KEY_F11:
   1033 	case KEY_F12:
   1034 	    show_fkey(key);
   1035 	    clear = 1;
   1036 	    break;
   1037 
   1038 	case KEY_TAB:
   1039 	    if (cm->allowedit && me->action == MA_CMD) {
   1040 		int ok = 1;
   1041 
   1042 		key_timeout = 0;	/* Cancels timeout */
   1043 		draw_row(entry - top + 4 + VSHIFT, -1, top, 0, 0);
   1044 
   1045 		if (cm->menu_master_passwd) {
   1046 		    ok = ask_passwd(NULL);
   1047 		    clear_screen();
   1048 		    draw_menu(-1, top, 0);
   1049 		} else {
   1050 		    /* Erase [Tab] message and help text */
   1051 		    printf("\033[%d;1H\1#0\033[K", TABMSG_ROW);
   1052 		    display_help(NULL);
   1053 		}
   1054 
   1055 		if (ok) {
   1056 		    cmdline = edit_cmdline(me->cmdline, top);
   1057 		    done = !!cmdline;
   1058 		    clear = 1;	/* In case we hit [Esc] and done is null */
   1059 		} else {
   1060 		    draw_row(entry - top + 4 + VSHIFT, entry, top, 0, 0);
   1061 		}
   1062 	    }
   1063 	    break;
   1064 	case KEY_CTRL('C'):	/* Ctrl-C */
   1065 	case KEY_ESC:		/* Esc */
   1066 	    if (cm->parent) {
   1067 		cm = cm->parent;
   1068 		clear = 2;
   1069 		entry = cm->curentry;
   1070 		top = cm->curtop;
   1071 	    } else if (cm->allowedit) {
   1072 		done = 1;
   1073 		clear = 1;
   1074 		key_timeout = 0;
   1075 
   1076 		draw_row(entry - top + 4 + VSHIFT, -1, top, 0, 0);
   1077 
   1078 		if (cm->menu_master_passwd)
   1079 		    done = ask_passwd(NULL);
   1080 	    }
   1081 	    break;
   1082 	default:
   1083 	    if (key > 0 && key < 0xFF) {
   1084 		key &= ~0x20;	/* Upper case */
   1085 		if (cm->menu_hotkeys[key]) {
   1086 		    key_timeout = 0;
   1087 		    entry = cm->menu_hotkeys[key]->entry;
   1088 		    /* Should we commit at this point? */
   1089 		    hotkey = true;
   1090 		}
   1091 	    }
   1092 	    break;
   1093 	}
   1094     }
   1095 
   1096     printf("\033[?25h");	/* Show cursor */
   1097 
   1098     /* Return the label name so localboot and ipappend work */
   1099     return cmdline;
   1100 }
   1101 
   1102 int main(int argc, char *argv[])
   1103 {
   1104     const char *cmdline;
   1105     struct menu *m;
   1106     int rows, cols;
   1107     int i;
   1108 
   1109     (void)argc;
   1110 
   1111     parse_configs(argv + 1);
   1112 
   1113     /*
   1114      * We don't start the console until we have parsed the configuration
   1115      * file, since the configuration file might impact the console
   1116      * configuration, e.g. MENU RESOLUTION.
   1117      */
   1118     start_console();
   1119     if (getscreensize(1, &rows, &cols)) {
   1120 	/* Unknown screen size? */
   1121 	rows = 24;
   1122 	cols = 80;
   1123     }
   1124 
   1125     /* Some postprocessing for all menus */
   1126     for (m = menu_list; m; m = m->next) {
   1127 	if (!m->mparm[P_WIDTH])
   1128 	    m->mparm[P_WIDTH] = cols;
   1129 
   1130 	/* If anyone has specified negative parameters, consider them
   1131 	   relative to the bottom row of the screen. */
   1132 	for (i = 0; i < NPARAMS; i++)
   1133 	    if (m->mparm[i] < 0)
   1134 		m->mparm[i] = max(m->mparm[i] + rows, 0);
   1135     }
   1136 
   1137     cm = start_menu;
   1138 
   1139     if (!cm->nentries) {
   1140 	fputs("Initial menu has no LABEL entries!\n", stdout);
   1141 	return 1;		/* Error! */
   1142     }
   1143 
   1144     for (;;) {
   1145 	local_cursor_enable(true);
   1146 	cmdline = run_menu();
   1147 
   1148 	if (clearmenu)
   1149 	    clear_screen();
   1150 
   1151 	local_cursor_enable(false);
   1152 	printf("\033[?25h\033[%d;1H\033[0m", END_ROW);
   1153 
   1154 	if (cmdline) {
   1155 	    uint32_t type = parse_image_type(cmdline);
   1156 
   1157 	    execute(cmdline, type, false);
   1158 	    if (cm->onerror) {
   1159 		type = parse_image_type(cm->onerror);
   1160 		execute(cm->onerror, type, true);
   1161 	    }
   1162 	} else {
   1163 	    return 0;		/* Exit */
   1164 	}
   1165     }
   1166 }
   1167