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