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