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