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... */
38static struct menu *cm;
39
40const 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
73static 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. */
96static void
97display_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
147static 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
177static jmp_buf timeout_jump;
178
179int 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
213static 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
300static 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
348static void clear_screen(void)
349{
350    fputs("\033e\033%@\033)0\033(B\1#0\033[?25l\033[2J", stdout);
351}
352
353static 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
387static 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
445static 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
612static 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. */
699static 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
706static 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
743static 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
1102int 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