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