Separate attribute control for disabled menu items
[syslinux.git] / com32 / modules / menumain.c
blob486857295b77c4dc581852be0c89981293176096
1 /* ----------------------------------------------------------------------- *
3 * Copyright 2004-2007 H. Peter Anvin - All Rights Reserved
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.
11 * ----------------------------------------------------------------------- */
14 * menumain.c
16 * Simple menu system which displays a list and allows the user to select
17 * a command line and/or edit it.
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 <md5.h>
32 #include <base64.h>
33 #include <colortbl.h>
34 #include <com32.h>
36 #include "menu.h"
39 * The color/attribute indexes (\1#X, \2#XX, \3#XXX) are as follows
41 * 00 - screen Rest of the screen
42 * 01 - border Border area
43 * 02 - title Title bar
44 * 03 - unsel Unselected menu item
45 * 04 - hotkey Unselected hotkey
46 * 05 - sel Selection bar
47 * 06 - hotsel Selected hotkey
48 * 07 - scrollbar Scroll bar
49 * 08 - tabmsg Press [Tab] message
50 * 09 - cmdmark Command line marker
51 * 10 - cmdline Command line
52 * 11 - pwdborder Password box border
53 * 12 - pwdheader Password box header
54 * 13 - pwdentry Password box contents
55 * 14 - timeout_msg Timeout message
56 * 15 - timeout Timeout counter
57 * 16 - help Current entry help text
58 * 17 - disabled Disabled menu item
61 static const struct color_table default_color_table[] = {
62 { "screen", "37;40", 0x80ffffff, 0x00000000, SHADOW_NORMAL },
63 { "border", "30;44", 0x40000000, 0x00000000, SHADOW_NORMAL },
64 { "title", "1;36;44", 0xc00090f0, 0x00000000, SHADOW_NORMAL },
65 { "unsel", "37;44", 0x90ffffff, 0x00000000, SHADOW_NORMAL },
66 { "hotkey", "1;37;44", 0xffffffff, 0x00000000, SHADOW_NORMAL },
67 { "sel", "7;37;40", 0xe0000000, 0x20ff8000, SHADOW_ALL },
68 { "hotsel", "1;7;37;40", 0xe0400000, 0x20ff8000, SHADOW_ALL },
69 { "scrollbar", "30;44", 0x40000000, 0x00000000, SHADOW_NORMAL },
70 { "tabmsg", "31;40", 0x90ffff00, 0x00000000, SHADOW_NORMAL },
71 { "cmdmark", "1;36;40", 0xc000ffff, 0x00000000, SHADOW_NORMAL },
72 { "cmdline", "37;40", 0xc0ffffff, 0x00000000, SHADOW_NORMAL },
73 { "pwdborder", "30;47", 0x80ffffff, 0x20ffffff, SHADOW_NORMAL },
74 { "pwdheader", "31;47", 0x80ff8080, 0x20ffffff, SHADOW_NORMAL },
75 { "pwdentry", "30;47", 0x80ffffff, 0x20ffffff, SHADOW_NORMAL },
76 { "timeout_msg", "37;40", 0x80ffffff, 0x00000000, SHADOW_NORMAL },
77 { "timeout", "1;37;40", 0xc0ffffff, 0x00000000, SHADOW_NORMAL },
78 { "help", "37;40", 0xc0ffffff, 0x00000000, SHADOW_NORMAL },
79 { "disabled", "1;30;44", 0x60cccccc, 0x00000000, SHADOW_NORMAL },
82 #define NCOLORS (sizeof default_color_table/sizeof(struct color_table))
83 const int message_base_color = NCOLORS;
85 struct menu_parameter mparm[] = {
86 { "width", 80 },
87 { "margin", 10 },
88 { "passwordmargin", 3 },
89 { "rows", 12 },
90 { "tabmsgrow", 18 },
91 { "cmdlinerow", 18 },
92 { "endrow", -1 },
93 { "passwordrow", 11 },
94 { "timeoutrow", 20 },
95 { "helpmsgrow", 22 },
96 { "helpmsgendrow", -1 },
97 { "hshift", 0 },
98 { "vshift", 0 },
99 { "hiddenrow", -2 },
100 { NULL, 0 }
103 #define WIDTH mparm[0].value
104 #define MARGIN mparm[1].value
105 #define PASSWD_MARGIN mparm[2].value
106 #define MENU_ROWS mparm[3].value
107 #define TABMSG_ROW (mparm[4].value+VSHIFT)
108 #define CMDLINE_ROW (mparm[5].value+VSHIFT)
109 #define END_ROW mparm[6].value
110 #define PASSWD_ROW (mparm[7].value+VSHIFT)
111 #define TIMEOUT_ROW (mparm[8].value+VSHIFT)
112 #define HELPMSG_ROW (mparm[9].value+VSHIFT)
113 #define HELPMSGEND_ROW mparm[10].value
114 #define HSHIFT mparm[11].value
115 #define VSHIFT mparm[12].value
116 #define HIDDEN_ROW mparm[13].value
118 void set_msg_colors_global(unsigned int fg, unsigned int bg,
119 enum color_table_shadow shadow)
121 struct color_table *cp = console_color_table+message_base_color;
122 unsigned int i;
123 unsigned int fga, bga;
124 unsigned int fgh, bgh;
125 unsigned int fg_idx, bg_idx;
126 unsigned int fg_rgb, bg_rgb;
128 static const unsigned int pc2rgb[8] =
129 { 0x000000, 0x0000ff, 0x00ff00, 0x00ffff, 0xff0000, 0xff00ff, 0xffff00,
130 0xffffff };
132 /* Converting PC RGBI to sensible RGBA values is an "interesting"
133 proposition. This algorithm may need plenty of tweaking. */
135 fga = fg & 0xff000000;
136 fgh = ((fg >> 1) & 0xff000000) | 0x80000000;
138 bga = bg & 0xff000000;
139 bgh = ((bg >> 1) & 0xff000000) | 0x80000000;
141 for (i = 0; i < 256; i++) {
142 fg_idx = i & 15;
143 bg_idx = i >> 4;
145 fg_rgb = pc2rgb[fg_idx & 7] & fg;
146 bg_rgb = pc2rgb[bg_idx & 7] & bg;
148 if (fg_idx & 8) {
149 /* High intensity foreground */
150 fg_rgb |= fgh;
151 } else {
152 fg_rgb |= fga;
155 if (bg_idx == 0) {
156 /* Default black background, assume transparent */
157 bg_rgb = 0;
158 } else if (bg_idx & 8) {
159 bg_rgb |= bgh;
160 } else {
161 bg_rgb |= bga;
164 cp->argb_fg = fg_rgb;
165 cp->argb_bg = bg_rgb;
166 cp->shadow = shadow;
167 cp++;
171 static void
172 install_default_color_table(void)
174 unsigned int i;
175 const struct color_table *dp;
176 struct color_table *cp;
177 static struct color_table color_table[NCOLORS+256];
178 static const int pc2ansi[8] = {0, 4, 2, 6, 1, 5, 3, 7};
180 dp = default_color_table;
181 cp = color_table;
183 for (i = 0; i < NCOLORS; i++) {
184 if (cp->ansi)
185 free((void *)cp->ansi);
187 *cp = *dp;
188 cp->ansi = strdup(dp->ansi);
190 cp++;
191 dp++;
194 for (i = 0; i < 256; i++) {
195 if (!cp->name)
196 asprintf((char **)&cp->name, "msg%02x", i);
198 if (cp->ansi)
199 free((void *)cp->ansi);
201 asprintf((char **)&cp->ansi, "%s3%d;4%d", (i & 8) ? "1;" : "",
202 pc2ansi[i & 7], pc2ansi[(i >> 4) & 7]);
204 cp++;
207 console_color_table = color_table;
208 console_color_table_size = NCOLORS+256;
210 set_msg_colors_global(MSG_COLORS_DEF_FG, MSG_COLORS_DEF_BG,
211 MSG_COLORS_DEF_SHADOW);
214 static char *
215 pad_line(const char *text, int align, int width)
217 static char buffer[MAX_CMDLINE_LEN];
218 int n, p;
220 if ( width >= (int) sizeof buffer )
221 return NULL; /* Can't do it */
223 n = strlen(text);
224 if ( n >= width )
225 n = width;
227 memset(buffer, ' ', width);
228 buffer[width] = 0;
229 p = ((width-n)*align)>>1;
230 memcpy(buffer+p, text, n);
232 return buffer;
235 /* Display an entry, with possible hotkey highlight. Assumes
236 that the current attribute is the non-hotkey one, and will
237 guarantee that as an exit condition as well. */
238 static void
239 display_entry(const struct menu_entry *entry, const char *attrib,
240 const char *hotattrib, int width)
242 const char *p = entry->displayname;
244 while ( width ) {
245 if ( *p ) {
246 if ( *p == '^' ) {
247 p++;
248 if ( *p && ((unsigned char)*p & ~0x20) == entry->hotkey ) {
249 fputs(hotattrib, stdout);
250 putchar(*p++);
251 fputs(attrib, stdout);
252 width--;
254 } else {
255 putchar(*p++);
256 width--;
258 } else {
259 putchar(' ');
260 width--;
265 static void
266 draw_row(int y, int sel, int top, int sbtop, int sbbot)
268 int i = (y-4-VSHIFT)+top;
269 int dis = (i < nentries) && menu_entries[i].disabled;
271 printf("\033[%d;%dH\1#1\016x\017%s ",
272 y, MARGIN+1+HSHIFT,
273 (i == sel) ? "\1#5" : dis ? "\2#17" : "\1#3");
275 if ( i >= nentries ) {
276 fputs(pad_line("", 0, WIDTH-2*MARGIN-4), stdout);
277 } else {
278 display_entry(&menu_entries[i],
279 (i == sel) ? "\1#5" : dis ? "\2#17" : "\1#3",
280 (i == sel) ? "\1#6" : dis ? "\2#17" : "\1#4",
281 WIDTH-2*MARGIN-4);
284 if ( nentries <= MENU_ROWS ) {
285 printf(" \1#1\016x\017");
286 } else if ( sbtop > 0 ) {
287 if ( y >= sbtop && y <= sbbot )
288 printf(" \1#7\016a\017");
289 else
290 printf(" \1#1\016x\017");
291 } else {
292 putchar(' '); /* Don't modify the scrollbar */
296 static int passwd_compare_sha1(const char *passwd, const char *entry)
298 const char *p;
299 SHA1_CTX ctx;
300 unsigned char sha1[20], pwdsha1[20];
302 if ( (p = strchr(passwd+3, '$')) ) {
303 SHA1Update(&ctx, (void *)passwd+3, p-(passwd+3));
304 p++;
305 } else {
306 p = passwd+3; /* Assume no salt */
309 SHA1Init(&ctx);
311 SHA1Update(&ctx, (void *)entry, strlen(entry));
312 SHA1Final(sha1, &ctx);
314 memset(pwdsha1, 0, 20);
315 unbase64(pwdsha1, 20, p);
317 return !memcmp(sha1, pwdsha1, 20);
320 static int passwd_compare_md5(const char *passwd, const char *entry)
322 const char *crypted = crypt_md5(entry, passwd+3);
323 int len = strlen(crypted);
325 return !strncmp(crypted, passwd, len) &&
326 (passwd[len] == '\0' || passwd[len] == '$');
329 static int
330 passwd_compare(const char *passwd, const char *entry)
332 if ( passwd[0] != '$' ) /* Plaintext passwd, yuck! */
333 return !strcmp(entry, passwd);
334 else if ( !strncmp(passwd, "$4$", 3) )
335 return passwd_compare_sha1(passwd, entry);
336 else if ( !strncmp(passwd, "$1$", 3) )
337 return passwd_compare_md5(passwd, entry);
338 else
339 return 0; /* Invalid encryption algorithm */
342 static jmp_buf timeout_jump;
344 int mygetkey(clock_t timeout)
346 clock_t t0, t;
347 clock_t tto, to;
348 int key;
350 if ( !totaltimeout )
351 return get_key(stdin, timeout);
353 for (;;) {
354 tto = min(totaltimeout, INT_MAX);
355 to = timeout ? min(tto, timeout) : tto;
357 t0 = times(NULL);
358 key = get_key(stdin, to);
359 t = times(NULL) - t0;
361 if ( totaltimeout <= t )
362 longjmp(timeout_jump, 1);
364 totaltimeout -= t;
366 if ( key != KEY_NONE )
367 return key;
369 if ( timeout ) {
370 if ( timeout <= t )
371 return KEY_NONE;
373 timeout -= t;
378 static int
379 ask_passwd(const char *menu_entry)
381 char user_passwd[WIDTH], *p;
382 int done;
383 int key;
384 int x;
386 printf("\033[%d;%dH\2#11\016l", PASSWD_ROW, PASSWD_MARGIN+1);
387 for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ )
388 putchar('q');
390 printf("k\033[%d;%dHx", PASSWD_ROW+1, PASSWD_MARGIN+1);
391 for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ )
392 putchar(' ');
394 printf("x\033[%d;%dHm", PASSWD_ROW+2, PASSWD_MARGIN+1);
395 for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ )
396 putchar('q');
398 printf("j\017\033[%d;%dH\2#12 %s \033[%d;%dH\2#13",
399 PASSWD_ROW, (WIDTH-(strlen(messages[MSG_PASSPROMPT].msg)+2))/2,
400 messages[MSG_PASSPROMPT].msg, PASSWD_ROW+1, PASSWD_MARGIN+3);
402 /* Actually allow user to type a password, then compare to the SHA1 */
403 done = 0;
404 p = user_passwd;
406 while ( !done ) {
407 key = mygetkey(0);
409 switch ( key ) {
410 case KEY_ENTER:
411 case KEY_CTRL('J'):
412 done = 1;
413 break;
415 case KEY_ESC:
416 case KEY_CTRL('C'):
417 p = user_passwd; /* No password entered */
418 done = 1;
419 break;
421 case KEY_BACKSPACE:
422 case KEY_DEL:
423 case KEY_DELETE:
424 if ( p > user_passwd ) {
425 printf("\b \b");
426 p--;
428 break;
430 case KEY_CTRL('U'):
431 while ( p > user_passwd ) {
432 printf("\b \b");
433 p--;
435 break;
437 default:
438 if ( key >= ' ' && key <= 0xFF &&
439 (p-user_passwd) < WIDTH-2*PASSWD_MARGIN-5 ) {
440 *p++ = key;
441 putchar('*');
443 break;
447 if ( p == user_passwd )
448 return 0; /* No password entered */
450 *p = '\0';
452 return (menu_master_passwd && passwd_compare(menu_master_passwd, user_passwd))
453 || (menu_entry && passwd_compare(menu_entry, user_passwd));
457 static void
458 draw_menu(int sel, int top, int edit_line)
460 int x, y;
461 int sbtop = 0, sbbot = 0;
462 const char *tabmsg;
463 int tabmsg_len;
465 if ( nentries > MENU_ROWS ) {
466 int sblen = MENU_ROWS*MENU_ROWS/nentries;
467 sbtop = (MENU_ROWS-sblen+1)*top/(nentries-MENU_ROWS+1);
468 sbbot = sbtop + sblen - 1;
470 sbtop += 4; sbbot += 4; /* Starting row of scrollbar */
473 printf("\033[%d;%dH\1#1\016l", VSHIFT+1, HSHIFT+MARGIN+1);
474 for ( x = 2+HSHIFT ; x <= (WIDTH-2*MARGIN-1)+HSHIFT ; x++ )
475 putchar('q');
477 printf("k\033[%d;%dH\1#1x\017\1#2 %s \1#1\016x",
478 VSHIFT+2,
479 HSHIFT+MARGIN+1,
480 pad_line(messages[MSG_TITLE].msg, 1, WIDTH-2*MARGIN-4));
482 printf("\033[%d;%dH\1#1t", VSHIFT+3, HSHIFT+MARGIN+1);
483 for ( x = 2+HSHIFT ; x <= (WIDTH-2*MARGIN-1)+HSHIFT ; x++ )
484 putchar('q');
485 fputs("u\017", stdout);
487 for ( y = 4+VSHIFT ; y < 4+VSHIFT+MENU_ROWS ; y++ )
488 draw_row(y, sel, top, sbtop, sbbot);
490 printf("\033[%d;%dH\1#1\016m", y, HSHIFT+MARGIN+1);
491 for ( x = 2+HSHIFT ; x <= (WIDTH-2*MARGIN-1)+HSHIFT ; x++ )
492 putchar('q');
493 fputs("j\017", stdout);
495 if ( edit_line && allowedit && !menu_master_passwd )
496 tabmsg = messages[MSG_TAB].msg;
497 else
498 tabmsg = messages[MSG_NOTAB].msg;
500 tabmsg_len = strlen(tabmsg);
502 printf("\1#8\033[%d;%dH%s",
503 TABMSG_ROW, 1+HSHIFT+((WIDTH-tabmsg_len)>>1), tabmsg);
504 printf("\1#0\033[%d;1H", END_ROW);
507 static void
508 clear_screen(void)
510 fputs("\033e\033%@\033)0\033(B\1#0\033[?25l\033[2J", stdout);
513 static void
514 display_help(const char *text)
516 int row;
517 const char *p;
519 if (!text) {
520 text = "";
521 printf("\1#0\033[%d;1H", HELPMSG_ROW);
522 } else {
523 printf("\2#16\033[%d;1H", HELPMSG_ROW);
526 for (p = text, row = HELPMSG_ROW; *p && row <= HELPMSGEND_ROW; p++) {
527 switch (*p) {
528 case '\r':
529 case '\f':
530 case '\v':
531 case '\033':
532 break;
533 case '\n':
534 printf("\033[K\033[%d;1H", ++row);
535 break;
536 default:
537 putchar(*p);
541 fputs("\033[K", stdout);
543 while (row <= HELPMSGEND_ROW) {
544 printf("\033[K\033[%d;1H", ++row);
548 static void show_fkey(int key)
550 int fkey;
552 while (1) {
553 switch (key) {
554 case KEY_F1: fkey = 0; break;
555 case KEY_F2: fkey = 1; break;
556 case KEY_F3: fkey = 2; break;
557 case KEY_F4: fkey = 3; break;
558 case KEY_F5: fkey = 4; break;
559 case KEY_F6: fkey = 5; break;
560 case KEY_F7: fkey = 6; break;
561 case KEY_F8: fkey = 7; break;
562 case KEY_F9: fkey = 8; break;
563 case KEY_F10: fkey = 9; break;
564 case KEY_F11: fkey = 10; break;
565 case KEY_F12: fkey = 11; break;
566 default: fkey = -1; break;
569 if (fkey == -1)
570 break;
572 if (fkeyhelp[fkey].textname)
573 key = show_message_file(fkeyhelp[fkey].textname,
574 fkeyhelp[fkey].background);
575 else
576 break;
580 static const char *
581 edit_cmdline(char *input, int top)
583 static char cmdline[MAX_CMDLINE_LEN];
584 int key, len, prev_len, cursor;
585 int redraw = 1; /* We enter with the menu already drawn */
587 strncpy(cmdline, input, MAX_CMDLINE_LEN);
588 cmdline[MAX_CMDLINE_LEN-1] = '\0';
590 len = cursor = strlen(cmdline);
591 prev_len = 0;
593 for (;;) {
594 if ( redraw > 1 ) {
595 /* Clear and redraw whole screen */
596 /* Enable ASCII on G0 and DEC VT on G1; do it in this order
597 to avoid confusing the Linux console */
598 clear_screen();
599 draw_menu(-1, top, 1);
600 prev_len = 0;
603 if ( redraw > 0 ) {
604 /* Redraw the command line */
605 printf("\033[?25l\033[%d;1H\1#9> \2#10%s",
606 CMDLINE_ROW, pad_line(cmdline, 0, max(len, prev_len)));
607 printf("\2#10\033[%d;3H%s\033[?25h",
608 CMDLINE_ROW, pad_line(cmdline, 0, cursor));
609 prev_len = len;
610 redraw = 0;
613 key = mygetkey(0);
615 switch( key ) {
616 case KEY_CTRL('L'):
617 redraw = 2;
618 break;
620 case KEY_ENTER:
621 case KEY_CTRL('J'):
622 return cmdline;
624 case KEY_ESC:
625 case KEY_CTRL('C'):
626 return NULL;
628 case KEY_BACKSPACE:
629 case KEY_DEL:
630 if ( cursor ) {
631 memmove(cmdline+cursor-1, cmdline+cursor, len-cursor+1);
632 len--;
633 cursor--;
634 redraw = 1;
636 break;
638 case KEY_CTRL('D'):
639 case KEY_DELETE:
640 if ( cursor < len ) {
641 memmove(cmdline+cursor, cmdline+cursor+1, len-cursor);
642 len--;
643 redraw = 1;
645 break;
647 case KEY_CTRL('U'):
648 if ( len ) {
649 len = cursor = 0;
650 cmdline[len] = '\0';
651 redraw = 1;
653 break;
655 case KEY_CTRL('W'):
656 if ( cursor ) {
657 int prevcursor = cursor;
659 while ( cursor && my_isspace(cmdline[cursor-1]) )
660 cursor--;
662 while ( cursor && !my_isspace(cmdline[cursor-1]) )
663 cursor--;
665 memmove(cmdline+cursor, cmdline+prevcursor, len-prevcursor+1);
666 len -= (cursor-prevcursor);
667 redraw = 1;
669 break;
671 case KEY_LEFT:
672 case KEY_CTRL('B'):
673 if ( cursor ) {
674 cursor--;
675 redraw = 1;
677 break;
679 case KEY_RIGHT:
680 case KEY_CTRL('F'):
681 if ( cursor < len ) {
682 putchar(cmdline[cursor++]);
684 break;
686 case KEY_CTRL('K'):
687 if ( cursor < len ) {
688 cmdline[len = cursor] = '\0';
689 redraw = 1;
691 break;
693 case KEY_HOME:
694 case KEY_CTRL('A'):
695 if ( cursor ) {
696 cursor = 0;
697 redraw = 1;
699 break;
701 case KEY_END:
702 case KEY_CTRL('E'):
703 if ( cursor != len ) {
704 cursor = len;
705 redraw = 1;
707 break;
709 case KEY_F1:
710 case KEY_F2:
711 case KEY_F3:
712 case KEY_F4:
713 case KEY_F5:
714 case KEY_F6:
715 case KEY_F7:
716 case KEY_F8:
717 case KEY_F9:
718 case KEY_F10:
719 case KEY_F11:
720 case KEY_F12:
721 show_fkey(key);
722 redraw = 1;
723 break;
725 default:
726 if ( key >= ' ' && key <= 0xFF && len < MAX_CMDLINE_LEN-1 ) {
727 if ( cursor == len ) {
728 cmdline[len] = key;
729 cmdline[++len] = '\0';
730 cursor++;
731 putchar(key);
732 prev_len++;
733 } else {
734 memmove(cmdline+cursor+1, cmdline+cursor, len-cursor+1);
735 cmdline[cursor++] = key;
736 len++;
737 redraw = 1;
740 break;
745 static inline int
746 shift_is_held(void)
748 uint8_t shift_bits = *(uint8_t *)0x417;
750 return !!(shift_bits & 0x5d); /* Caps/Scroll/Alt/Shift */
753 static void
754 print_timeout_message(int tol, int row, const char *msg)
756 char buf[256];
757 int nc = 0, nnc;
758 const char *tp = msg;
759 char tc;
760 char *tq = buf;
762 while ((size_t)(tq-buf) < (sizeof buf-16) && (tc = *tp)) {
763 tp++;
764 if (tc == '#') {
765 nnc = sprintf(tq, "\2#15%d\2#14", tol);
766 tq += nnc;
767 nc += nnc-8; /* 8 formatting characters */
768 } else if (tc == '{') {
769 /* Deal with {singular[,dual],plural} constructs */
770 struct {
771 const char *s, *e;
772 } tx[3];
773 const char *tpp;
774 int n = 0;
776 memset(tx, 0, sizeof tx);
778 tx[0].s = tp;
780 while (*tp && *tp != '}') {
781 if (*tp == ',' && n < 2) {
782 tx[n].e = tp;
783 n++;
784 tx[n].s = tp+1;
786 tp++;
788 tx[n].e = tp;
790 if (*tp)
791 tp++; /* Skip final bracket */
793 if (!tx[1].s)
794 tx[1] = tx[0];
795 if (!tx[2].s)
796 tx[2] = tx[1];
798 /* Now [0] is singular, [1] is dual, and [2] is plural,
799 even if the user only specified some of them. */
801 switch (tol) {
802 case 1: n = 0; break;
803 case 2: n = 1; break;
804 default: n = 2; break;
807 for (tpp = tx[n].s; tpp < tx[n].e; tpp++) {
808 if ((size_t)(tq-buf) < (sizeof buf)) {
809 *tq++ = *tpp;
810 nc++;
813 } else {
814 *tq++ = tc;
815 nc++;
818 *tq = '\0';
820 /* Let's hope 4 spaces on each side is enough... */
821 printf("\033[%d;%dH\2#14 %s ", row, HSHIFT+1+((WIDTH-nc-8)>>1), buf);
824 static const char *
825 do_hidden_menu(void)
827 int key;
828 int timeout_left, this_timeout;
830 clear_screen();
832 if ( !setjmp(timeout_jump) ) {
833 timeout_left = timeout;
835 while (!timeout || timeout_left) {
836 int tol = timeout_left/CLK_TCK;
838 print_timeout_message(tol, HIDDEN_ROW, messages[MSG_AUTOBOOT].msg);
840 this_timeout = min(timeout_left, CLK_TCK);
841 key = mygetkey(this_timeout);
843 if (key != KEY_NONE)
844 return NULL; /* Key pressed */
846 timeout_left -= this_timeout;
850 return menu_entries[defentry].cmdline; /* Default entry */
853 static const char *
854 run_menu(void)
856 int key;
857 int done = 0;
858 volatile int entry = defentry, prev_entry = -1;
859 int top = 0, prev_top = -1;
860 int clear = 1, to_clear;
861 const char *cmdline = NULL;
862 volatile clock_t key_timeout, timeout_left, this_timeout;
864 /* Note: for both key_timeout and timeout == 0 means no limit */
865 timeout_left = key_timeout = timeout;
867 /* If we're in shiftkey mode, exit immediately unless a shift key is pressed */
868 if ( shiftkey && !shift_is_held() ) {
869 return menu_entries[defentry].cmdline;
872 /* Handle hiddenmenu */
873 if ( hiddenmenu ) {
874 cmdline = do_hidden_menu();
875 if (cmdline)
876 return cmdline;
878 /* Otherwise display the menu now; the timeout has already been
879 cancelled, since the user pressed a key. */
880 hiddenmenu = 0;
881 key_timeout = 0;
884 /* Handle both local and global timeout */
885 if ( setjmp(timeout_jump) ) {
886 entry = defentry;
888 if ( top < 0 || top < entry-MENU_ROWS+1 )
889 top = max(0, entry-MENU_ROWS+1);
890 else if ( top > entry || top > max(0,nentries-MENU_ROWS) )
891 top = min(entry, max(0,nentries-MENU_ROWS));
893 draw_menu(ontimeout ? -1 : entry, top, 1);
894 cmdline = ontimeout ? ontimeout : menu_entries[entry].cmdline;
895 done = 1;
898 while ( !done ) {
899 if ( entry <= 0 ) {
900 entry = 0;
901 while ( entry < nentries && menu_entries[entry].disabled ) entry++;
904 if ( entry >= nentries ) {
905 entry = nentries-1;
906 while ( entry > 0 && menu_entries[entry].disabled ) entry--;
909 if ( top < 0 || top < entry-MENU_ROWS+1 )
910 top = max(0, entry-MENU_ROWS+1);
911 else if ( top > entry || top > max(0,nentries-MENU_ROWS) )
912 top = min(entry, max(0,nentries-MENU_ROWS));
914 /* Start with a clear screen */
915 if ( clear ) {
916 /* Clear and redraw whole screen */
917 /* Enable ASCII on G0 and DEC VT on G1; do it in this order
918 to avoid confusing the Linux console */
919 clear_screen();
920 clear = 0;
921 prev_entry = prev_top = -1;
924 if ( top != prev_top ) {
925 draw_menu(entry, top, 1);
926 display_help(menu_entries[entry].helptext);
927 } else if ( entry != prev_entry ) {
928 draw_row(prev_entry-top+4+VSHIFT, entry, top, 0, 0);
929 draw_row(entry-top+4+VSHIFT, entry, top, 0, 0);
930 display_help(menu_entries[entry].helptext);
933 prev_entry = entry; prev_top = top;
935 /* Cursor movement cancels timeout */
936 if ( entry != defentry )
937 key_timeout = 0;
939 if ( key_timeout ) {
940 int tol = timeout_left/CLK_TCK;
941 print_timeout_message(tol, TIMEOUT_ROW, messages[MSG_AUTOBOOT].msg);
942 to_clear = 1;
943 } else {
944 to_clear = 0;
947 this_timeout = min(min(key_timeout, timeout_left), CLK_TCK);
948 key = mygetkey(this_timeout);
950 if ( key != KEY_NONE ) {
951 timeout_left = key_timeout;
952 if ( to_clear )
953 printf("\033[%d;1H\1#0\033[K", TIMEOUT_ROW);
956 switch ( key ) {
957 case KEY_NONE: /* Timeout */
958 /* This is somewhat hacky, but this at least lets the user
959 know what's going on, and still deals with "phantom inputs"
960 e.g. on serial ports.
962 Warning: a timeout will boot the default entry without any
963 password! */
964 if ( key_timeout ) {
965 if ( timeout_left <= this_timeout )
966 longjmp(timeout_jump, 1);
968 timeout_left -= this_timeout;
970 break;
972 case KEY_CTRL('L'):
973 clear = 1;
974 break;
976 case KEY_ENTER:
977 case KEY_CTRL('J'):
978 key_timeout = 0; /* Cancels timeout */
979 if ( menu_entries[entry].passwd ) {
980 clear = 1;
981 done = ask_passwd(menu_entries[entry].passwd);
982 } else {
983 done = 1;
985 cmdline = menu_entries[entry].cmdline;
986 break;
988 case KEY_UP:
989 case KEY_CTRL('P'):
990 while ( entry > 0 && entry-- && menu_entries[entry].disabled ) {
991 if ( entry < top )
992 top -= MENU_ROWS;
995 if ( entry == 0 ) {
996 while ( menu_entries[entry].disabled )
997 entry++;
999 break;
1001 case KEY_DOWN:
1002 case KEY_CTRL('N'):
1003 while ( entry < nentries-1 && entry++ && menu_entries[entry].disabled ) {
1004 if ( entry >= top+MENU_ROWS )
1005 top += MENU_ROWS;
1008 if ( entry == nentries-1 ) {
1009 while ( menu_entries[entry].disabled )
1010 entry--;
1012 break;
1014 case KEY_PGUP:
1015 case KEY_LEFT:
1016 case KEY_CTRL('B'):
1017 case '<':
1018 entry -= MENU_ROWS;
1019 top -= MENU_ROWS;
1020 break;
1022 case KEY_PGDN:
1023 case KEY_RIGHT:
1024 case KEY_CTRL('F'):
1025 case '>':
1026 case ' ':
1027 entry += MENU_ROWS;
1028 top += MENU_ROWS;
1029 break;
1031 case '-':
1032 entry--;
1033 top--;
1034 break;
1036 case '+':
1037 entry++;
1038 top++;
1039 break;
1041 case KEY_CTRL('A'):
1042 case KEY_HOME:
1043 top = entry = 0;
1044 break;
1046 case KEY_CTRL('E'):
1047 case KEY_END:
1048 entry = nentries - 1;
1049 top = max(0, nentries-MENU_ROWS);
1050 break;
1052 case KEY_F1:
1053 case KEY_F2:
1054 case KEY_F3:
1055 case KEY_F4:
1056 case KEY_F5:
1057 case KEY_F6:
1058 case KEY_F7:
1059 case KEY_F8:
1060 case KEY_F9:
1061 case KEY_F10:
1062 case KEY_F11:
1063 case KEY_F12:
1064 show_fkey(key);
1065 clear = 1;
1066 break;
1068 case KEY_TAB:
1069 if ( allowedit ) {
1070 int ok = 1;
1072 key_timeout = 0; /* Cancels timeout */
1073 draw_row(entry-top+4+VSHIFT, -1, top, 0, 0);
1075 if ( menu_master_passwd ) {
1076 ok = ask_passwd(NULL);
1077 clear_screen();
1078 draw_menu(-1, top, 0);
1079 } else {
1080 /* Erase [Tab] message and help text*/
1081 printf("\033[%d;1H\1#0\033[K", TABMSG_ROW);
1082 display_help(NULL);
1085 if ( ok ) {
1086 cmdline = edit_cmdline(menu_entries[entry].cmdline, top);
1087 done = !!cmdline;
1088 clear = 1; /* In case we hit [Esc] and done is null */
1089 } else {
1090 draw_row(entry-top+4+VSHIFT, entry, top, 0, 0);
1093 break;
1094 case KEY_CTRL('C'): /* Ctrl-C */
1095 case KEY_ESC: /* Esc */
1096 if ( allowedit ) {
1097 done = 1;
1098 clear = 1;
1099 key_timeout = 0;
1101 draw_row(entry-top+4+VSHIFT, -1, top, 0, 0);
1103 if ( menu_master_passwd )
1104 done = ask_passwd(NULL);
1106 break;
1107 default:
1108 if ( key > 0 && key < 0xFF ) {
1109 key &= ~0x20; /* Upper case */
1110 if ( menu_hotkeys[key] ) {
1111 key_timeout = 0;
1112 entry = menu_hotkeys[key] - menu_entries;
1113 /* Should we commit at this point? */
1116 break;
1120 printf("\033[?25h"); /* Show cursor */
1122 /* Return the label name so localboot and ipappend work */
1123 return cmdline;
1127 static void
1128 execute(const char *cmdline, enum kernel_type type)
1130 com32sys_t ireg;
1131 const char *p, **pp;
1132 char *q = __com32.cs_bounce;
1133 const char *kernel, *args;
1135 memset(&ireg, 0, sizeof ireg);
1137 kernel = q;
1138 p = cmdline;
1139 while ( *p && !my_isspace(*p) ) {
1140 *q++ = *p++;
1142 *q++ = '\0';
1144 args = q;
1145 while ( *p && my_isspace(*p) )
1146 p++;
1148 strcpy(q, p);
1150 if (kernel[0] == '.' && type == KT_NONE) {
1151 /* It might be a type specifier */
1152 enum kernel_type type = KT_NONE;
1153 for (pp = kernel_types; *pp; pp++, type++) {
1154 if (!strcmp(kernel+1, *pp)) {
1155 execute(p, type); /* Strip the type specifier and retry */
1160 if (type == KT_LOCALBOOT) {
1161 ireg.eax.w[0] = 0x0014; /* Local boot */
1162 ireg.edx.w[0] = strtoul(kernel, NULL, 0);
1163 } else {
1164 if (type < KT_KERNEL)
1165 type = KT_KERNEL;
1167 ireg.eax.w[0] = 0x0016; /* Run kernel image */
1168 ireg.esi.w[0] = OFFS(kernel);
1169 ireg.ds = SEG(kernel);
1170 ireg.ebx.w[0] = OFFS(args);
1171 ireg.es = SEG(args);
1172 ireg.edx.l = type-KT_KERNEL;
1173 /* ireg.ecx.l = 0; */ /* We do ipappend "manually" */
1176 __intcall(0x22, &ireg, NULL);
1178 /* If this returns, something went bad; return to menu */
1181 int menu_main(int argc, char *argv[])
1183 const char *cmdline;
1184 int rows, cols;
1185 int i;
1187 (void)argc;
1189 console_prepare();
1191 install_default_color_table();
1192 if (getscreensize(1, &rows, &cols)) {
1193 /* Unknown screen size? */
1194 rows = 24;
1195 cols = 80;
1198 WIDTH = cols;
1199 parse_configs(argv+1);
1201 /* If anyone has specified negative parameters, consider them
1202 relative to the bottom row of the screen. */
1203 for (i = 0; mparm[i].name; i++)
1204 if (mparm[i].value < 0)
1205 mparm[i].value = max(mparm[i].value+rows, 0);
1207 draw_background(menu_background);
1209 if ( !nentries ) {
1210 fputs("No LABEL entries found in configuration file!\n", stdout);
1211 return 1; /* Error! */
1214 for(;;) {
1215 cmdline = run_menu();
1217 printf("\033[?25h\033[%d;1H\033[0m", END_ROW);
1218 console_cleanup();
1220 if ( cmdline ) {
1221 execute(cmdline, KT_NONE);
1222 if ( onerror )
1223 execute(onerror, KT_NONE);
1224 } else {
1225 return 0; /* Exit */
1228 console_prepare(); /* If we're looping... */