Adding upstream version 3.53.
[syslinux-debian/hramrach.git] / com32 / modules / menumain.c
blobb28371ef38b9b4aa24a3d255f1fc30c11f526c83
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
60 static const struct color_table default_color_table[] = {
61 { "screen", "37;40", 0x80ffffff, 0x00000000, SHADOW_NORMAL },
62 { "border", "30;44", 0x40000000, 0x00000000, SHADOW_NORMAL },
63 { "title", "1;36;44", 0xc00090f0, 0x00000000, SHADOW_NORMAL },
64 { "unsel", "37;44", 0x90ffffff, 0x00000000, SHADOW_NORMAL },
65 { "hotkey", "1;37;44", 0xffffffff, 0x00000000, SHADOW_NORMAL },
66 { "sel", "7;37;40", 0xe0000000, 0x20ff8000, SHADOW_ALL },
67 { "hotsel", "1;7;37;40", 0xe0400000, 0x20ff8000, SHADOW_ALL },
68 { "scrollbar", "30;44", 0x40000000, 0x00000000, SHADOW_NORMAL },
69 { "tabmsg", "31;40", 0x90ffff00, 0x00000000, SHADOW_NORMAL },
70 { "cmdmark", "1;36;40", 0xc000ffff, 0x00000000, SHADOW_NORMAL },
71 { "cmdline", "37;40", 0xc0ffffff, 0x00000000, SHADOW_NORMAL },
72 { "pwdborder", "30;47", 0x80ffffff, 0x20ffffff, SHADOW_NORMAL },
73 { "pwdheader", "31;47", 0x80ff8080, 0x20ffffff, SHADOW_NORMAL },
74 { "pwdentry", "30;47", 0x80ffffff, 0x20ffffff, SHADOW_NORMAL },
75 { "timeout_msg", "37;40", 0x80ffffff, 0x00000000, SHADOW_NORMAL },
76 { "timeout", "1;37;40", 0xc0ffffff, 0x00000000, SHADOW_NORMAL },
77 { "help", "37;40", 0xc0ffffff, 0x00000000, SHADOW_NORMAL },
80 #define NCOLORS (sizeof default_color_table/sizeof(struct color_table))
81 const int message_base_color = NCOLORS;
83 struct menu_parameter mparm[] = {
84 { "width", 80 },
85 { "margin", 10 },
86 { "passwordmargin", 3 },
87 { "rows", 12 },
88 { "tabmsgrow", 18 },
89 { "cmdlinerow", 18 },
90 { "endrow", -1 },
91 { "passwordrow", 11 },
92 { "timeoutrow", 20 },
93 { "helpmsgrow", 22 },
94 { "helpmsgendrow", -1 },
95 { "hshift", 0 },
96 { "vshift", 0 },
97 { "hiddenrow", -2 },
98 { NULL, 0 }
101 #define WIDTH mparm[0].value
102 #define MARGIN mparm[1].value
103 #define PASSWD_MARGIN mparm[2].value
104 #define MENU_ROWS mparm[3].value
105 #define TABMSG_ROW (mparm[4].value+VSHIFT)
106 #define CMDLINE_ROW (mparm[5].value+VSHIFT)
107 #define END_ROW mparm[6].value
108 #define PASSWD_ROW (mparm[7].value+VSHIFT)
109 #define TIMEOUT_ROW (mparm[8].value+VSHIFT)
110 #define HELPMSG_ROW (mparm[9].value+VSHIFT)
111 #define HELPMSGEND_ROW mparm[10].value
112 #define HSHIFT mparm[11].value
113 #define VSHIFT mparm[12].value
114 #define HIDDEN_ROW mparm[13].value
116 void set_msg_colors_global(unsigned int fg, unsigned int bg,
117 enum color_table_shadow shadow)
119 struct color_table *cp = console_color_table+message_base_color;
120 unsigned int i;
121 unsigned int fga, bga;
122 unsigned int fgh, bgh;
123 unsigned int fg_idx, bg_idx;
124 unsigned int fg_rgb, bg_rgb;
126 static const unsigned int pc2rgb[8] =
127 { 0x000000, 0x0000ff, 0x00ff00, 0x00ffff, 0xff0000, 0xff00ff, 0xffff00,
128 0xffffff };
130 /* Converting PC RGBI to sensible RGBA values is an "interesting"
131 proposition. This algorithm may need plenty of tweaking. */
133 fga = fg & 0xff000000;
134 fgh = ((fg >> 1) & 0xff000000) | 0x80000000;
136 bga = bg & 0xff000000;
137 bgh = ((bg >> 1) & 0xff000000) | 0x80000000;
139 for (i = 0; i < 256; i++) {
140 fg_idx = i & 15;
141 bg_idx = i >> 4;
143 fg_rgb = pc2rgb[fg_idx & 7] & fg;
144 bg_rgb = pc2rgb[bg_idx & 7] & bg;
146 if (fg_idx & 8) {
147 /* High intensity foreground */
148 fg_rgb |= fgh;
149 } else {
150 fg_rgb |= fga;
153 if (bg_idx == 0) {
154 /* Default black background, assume transparent */
155 bg_rgb = 0;
156 } else if (bg_idx & 8) {
157 bg_rgb |= bgh;
158 } else {
159 bg_rgb |= bga;
162 cp->argb_fg = fg_rgb;
163 cp->argb_bg = bg_rgb;
164 cp->shadow = shadow;
165 cp++;
169 static void
170 install_default_color_table(void)
172 unsigned int i;
173 const struct color_table *dp;
174 struct color_table *cp;
175 static struct color_table color_table[NCOLORS+256];
176 static const int pc2ansi[8] = {0, 4, 2, 6, 1, 5, 3, 7};
178 dp = default_color_table;
179 cp = color_table;
181 for (i = 0; i < NCOLORS; i++) {
182 if (cp->ansi)
183 free((void *)cp->ansi);
185 *cp = *dp;
186 cp->ansi = strdup(dp->ansi);
188 cp++;
189 dp++;
192 for (i = 0; i < 256; i++) {
193 if (!cp->name)
194 asprintf((char **)&cp->name, "msg%02x", i);
196 if (cp->ansi)
197 free((void *)cp->ansi);
199 asprintf((char **)&cp->ansi, "%s3%d;4%d", (i & 8) ? "1;" : "",
200 pc2ansi[i & 7], pc2ansi[(i >> 4) & 7]);
202 cp++;
205 console_color_table = color_table;
206 console_color_table_size = NCOLORS+256;
208 set_msg_colors_global(MSG_COLORS_DEF_FG, MSG_COLORS_DEF_BG,
209 MSG_COLORS_DEF_SHADOW);
212 static char *
213 pad_line(const char *text, int align, int width)
215 static char buffer[MAX_CMDLINE_LEN];
216 int n, p;
218 if ( width >= (int) sizeof buffer )
219 return NULL; /* Can't do it */
221 n = strlen(text);
222 if ( n >= width )
223 n = width;
225 memset(buffer, ' ', width);
226 buffer[width] = 0;
227 p = ((width-n)*align)>>1;
228 memcpy(buffer+p, text, n);
230 return buffer;
233 /* Display an entry, with possible hotkey highlight. Assumes
234 that the current attribute is the non-hotkey one, and will
235 guarantee that as an exit condition as well. */
236 static void
237 display_entry(const struct menu_entry *entry, const char *attrib,
238 const char *hotattrib, int width)
240 const char *p = entry->displayname;
242 while ( width ) {
243 if ( *p ) {
244 if ( *p == '^' ) {
245 p++;
246 if ( *p && ((unsigned char)*p & ~0x20) == entry->hotkey ) {
247 fputs(hotattrib, stdout);
248 putchar(*p++);
249 fputs(attrib, stdout);
250 width--;
252 } else {
253 putchar(*p++);
254 width--;
256 } else {
257 putchar(' ');
258 width--;
263 static void
264 draw_row(int y, int sel, int top, int sbtop, int sbbot)
266 int i = (y-4-VSHIFT)+top;
268 printf("\033[%d;%dH\1#1\016x\017%s ",
269 y, MARGIN+1+HSHIFT, (i == sel) ? "\1#5" : "\1#3");
271 if ( i >= nentries ) {
272 fputs(pad_line("", 0, WIDTH-2*MARGIN-4), stdout);
273 } else {
274 display_entry(&menu_entries[i],
275 (i == sel) ? "\1#5" : "\1#3",
276 (i == sel) ? "\1#6" : "\1#4",
277 WIDTH-2*MARGIN-4);
280 if ( nentries <= MENU_ROWS ) {
281 printf(" \1#1\016x\017");
282 } else if ( sbtop > 0 ) {
283 if ( y >= sbtop && y <= sbbot )
284 printf(" \1#7\016a\017");
285 else
286 printf(" \1#1\016x\017");
287 } else {
288 putchar(' '); /* Don't modify the scrollbar */
292 static int passwd_compare_sha1(const char *passwd, const char *entry)
294 const char *p;
295 SHA1_CTX ctx;
296 unsigned char sha1[20], pwdsha1[20];
298 if ( (p = strchr(passwd+3, '$')) ) {
299 SHA1Update(&ctx, (void *)passwd+3, p-(passwd+3));
300 p++;
301 } else {
302 p = passwd+3; /* Assume no salt */
305 SHA1Init(&ctx);
307 SHA1Update(&ctx, (void *)entry, strlen(entry));
308 SHA1Final(sha1, &ctx);
310 memset(pwdsha1, 0, 20);
311 unbase64(pwdsha1, 20, p);
313 return !memcmp(sha1, pwdsha1, 20);
316 static int passwd_compare_md5(const char *passwd, const char *entry)
318 const char *crypted = crypt_md5(entry, passwd+3);
319 int len = strlen(crypted);
321 return !strncmp(crypted, passwd, len) &&
322 (passwd[len] == '\0' || passwd[len] == '$');
325 static int
326 passwd_compare(const char *passwd, const char *entry)
328 if ( passwd[0] != '$' ) /* Plaintext passwd, yuck! */
329 return !strcmp(entry, passwd);
330 else if ( !strncmp(passwd, "$4$", 3) )
331 return passwd_compare_sha1(passwd, entry);
332 else if ( !strncmp(passwd, "$1$", 3) )
333 return passwd_compare_md5(passwd, entry);
334 else
335 return 0; /* Invalid encryption algorithm */
338 static jmp_buf timeout_jump;
340 int mygetkey(clock_t timeout)
342 clock_t t0, t;
343 clock_t tto, to;
344 int key;
346 if ( !totaltimeout )
347 return get_key(stdin, timeout);
349 for (;;) {
350 tto = min(totaltimeout, INT_MAX);
351 to = timeout ? min(tto, timeout) : tto;
353 t0 = times(NULL);
354 key = get_key(stdin, to);
355 t = times(NULL) - t0;
357 if ( totaltimeout <= t )
358 longjmp(timeout_jump, 1);
360 totaltimeout -= t;
362 if ( key != KEY_NONE )
363 return key;
365 if ( timeout ) {
366 if ( timeout <= t )
367 return KEY_NONE;
369 timeout -= t;
374 static int
375 ask_passwd(const char *menu_entry)
377 char user_passwd[WIDTH], *p;
378 int done;
379 int key;
380 int x;
382 printf("\033[%d;%dH\2#11\016l", PASSWD_ROW, PASSWD_MARGIN+1);
383 for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ )
384 putchar('q');
386 printf("k\033[%d;%dHx", PASSWD_ROW+1, PASSWD_MARGIN+1);
387 for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ )
388 putchar(' ');
390 printf("x\033[%d;%dHm", PASSWD_ROW+2, PASSWD_MARGIN+1);
391 for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ )
392 putchar('q');
394 printf("j\017\033[%d;%dH\2#12 %s \033[%d;%dH\2#13",
395 PASSWD_ROW, (WIDTH-(strlen(messages[MSG_PASSPROMPT].msg)+2))/2,
396 messages[MSG_PASSPROMPT].msg, PASSWD_ROW+1, PASSWD_MARGIN+3);
398 /* Actually allow user to type a password, then compare to the SHA1 */
399 done = 0;
400 p = user_passwd;
402 while ( !done ) {
403 key = mygetkey(0);
405 switch ( key ) {
406 case KEY_ENTER:
407 case KEY_CTRL('J'):
408 done = 1;
409 break;
411 case KEY_ESC:
412 case KEY_CTRL('C'):
413 p = user_passwd; /* No password entered */
414 done = 1;
415 break;
417 case KEY_BACKSPACE:
418 case KEY_DEL:
419 case KEY_DELETE:
420 if ( p > user_passwd ) {
421 printf("\b \b");
422 p--;
424 break;
426 case KEY_CTRL('U'):
427 while ( p > user_passwd ) {
428 printf("\b \b");
429 p--;
431 break;
433 default:
434 if ( key >= ' ' && key <= 0xFF &&
435 (p-user_passwd) < WIDTH-2*PASSWD_MARGIN-5 ) {
436 *p++ = key;
437 putchar('*');
439 break;
443 if ( p == user_passwd )
444 return 0; /* No password entered */
446 *p = '\0';
448 return (menu_master_passwd && passwd_compare(menu_master_passwd, user_passwd))
449 || (menu_entry && passwd_compare(menu_entry, user_passwd));
453 static void
454 draw_menu(int sel, int top, int edit_line)
456 int x, y;
457 int sbtop = 0, sbbot = 0;
458 const char *tabmsg;
459 int tabmsg_len;
461 if ( nentries > MENU_ROWS ) {
462 int sblen = MENU_ROWS*MENU_ROWS/nentries;
463 sbtop = (MENU_ROWS-sblen+1)*top/(nentries-MENU_ROWS+1);
464 sbbot = sbtop + sblen - 1;
466 sbtop += 4; sbbot += 4; /* Starting row of scrollbar */
469 printf("\033[%d;%dH\1#1\016l", VSHIFT+1, HSHIFT+MARGIN+1);
470 for ( x = 2+HSHIFT ; x <= (WIDTH-2*MARGIN-1)+HSHIFT ; x++ )
471 putchar('q');
473 printf("k\033[%d;%dH\1#1x\017\1#2 %s \1#1\016x",
474 VSHIFT+2,
475 HSHIFT+MARGIN+1,
476 pad_line(messages[MSG_TITLE].msg, 1, WIDTH-2*MARGIN-4));
478 printf("\033[%d;%dH\1#1t", VSHIFT+3, HSHIFT+MARGIN+1);
479 for ( x = 2+HSHIFT ; x <= (WIDTH-2*MARGIN-1)+HSHIFT ; x++ )
480 putchar('q');
481 fputs("u\017", stdout);
483 for ( y = 4+VSHIFT ; y < 4+VSHIFT+MENU_ROWS ; y++ )
484 draw_row(y, sel, top, sbtop, sbbot);
486 printf("\033[%d;%dH\1#1\016m", y, HSHIFT+MARGIN+1);
487 for ( x = 2+HSHIFT ; x <= (WIDTH-2*MARGIN-1)+HSHIFT ; x++ )
488 putchar('q');
489 fputs("j\017", stdout);
491 if ( edit_line && allowedit && !menu_master_passwd )
492 tabmsg = messages[MSG_TAB].msg;
493 else
494 tabmsg = messages[MSG_NOTAB].msg;
496 tabmsg_len = strlen(tabmsg);
498 printf("\1#8\033[%d;%dH%s",
499 TABMSG_ROW, 1+HSHIFT+((WIDTH-tabmsg_len)>>1), tabmsg);
500 printf("\1#0\033[%d;1H", END_ROW);
503 static void
504 clear_screen(void)
506 fputs("\033e\033%@\033)0\033(B\1#0\033[?25l\033[2J", stdout);
509 static void
510 display_help(const char *text)
512 int row;
513 const char *p;
515 if (!text) {
516 text = "";
517 printf("\1#0\033[%d;1H", HELPMSG_ROW);
518 } else {
519 printf("\2#16\033[%d;1H", HELPMSG_ROW);
522 for (p = text, row = HELPMSG_ROW; *p && row <= HELPMSGEND_ROW; p++) {
523 switch (*p) {
524 case '\r':
525 case '\f':
526 case '\v':
527 case '\033':
528 break;
529 case '\n':
530 printf("\033[K\033[%d;1H", ++row);
531 break;
532 default:
533 putchar(*p);
537 fputs("\033[K", stdout);
539 while (row <= HELPMSGEND_ROW) {
540 printf("\033[K\033[%d;1H", ++row);
544 static void show_fkey(int key)
546 int fkey;
548 while (1) {
549 switch (key) {
550 case KEY_F1: fkey = 0; break;
551 case KEY_F2: fkey = 1; break;
552 case KEY_F3: fkey = 2; break;
553 case KEY_F4: fkey = 3; break;
554 case KEY_F5: fkey = 4; break;
555 case KEY_F6: fkey = 5; break;
556 case KEY_F7: fkey = 6; break;
557 case KEY_F8: fkey = 7; break;
558 case KEY_F9: fkey = 8; break;
559 case KEY_F10: fkey = 9; break;
560 case KEY_F11: fkey = 10; break;
561 case KEY_F12: fkey = 11; break;
562 default: fkey = -1; break;
565 if (fkey == -1)
566 break;
568 if (fkeyhelp[fkey].textname)
569 key = show_message_file(fkeyhelp[fkey].textname,
570 fkeyhelp[fkey].background);
571 else
572 break;
576 static const char *
577 edit_cmdline(char *input, int top)
579 static char cmdline[MAX_CMDLINE_LEN];
580 int key, len, prev_len, cursor;
581 int redraw = 1; /* We enter with the menu already drawn */
583 strncpy(cmdline, input, MAX_CMDLINE_LEN);
584 cmdline[MAX_CMDLINE_LEN-1] = '\0';
586 len = cursor = strlen(cmdline);
587 prev_len = 0;
589 for (;;) {
590 if ( redraw > 1 ) {
591 /* Clear and redraw whole screen */
592 /* Enable ASCII on G0 and DEC VT on G1; do it in this order
593 to avoid confusing the Linux console */
594 clear_screen();
595 draw_menu(-1, top, 1);
596 prev_len = 0;
599 if ( redraw > 0 ) {
600 /* Redraw the command line */
601 printf("\033[?25l\033[%d;1H\1#9> \2#10%s",
602 CMDLINE_ROW, pad_line(cmdline, 0, max(len, prev_len)));
603 printf("\2#10\033[%d;3H%s\033[?25h",
604 CMDLINE_ROW, pad_line(cmdline, 0, cursor));
605 prev_len = len;
606 redraw = 0;
609 key = mygetkey(0);
611 switch( key ) {
612 case KEY_CTRL('L'):
613 redraw = 2;
614 break;
616 case KEY_ENTER:
617 case KEY_CTRL('J'):
618 return cmdline;
620 case KEY_ESC:
621 case KEY_CTRL('C'):
622 return NULL;
624 case KEY_BACKSPACE:
625 case KEY_DEL:
626 if ( cursor ) {
627 memmove(cmdline+cursor-1, cmdline+cursor, len-cursor+1);
628 len--;
629 cursor--;
630 redraw = 1;
632 break;
634 case KEY_CTRL('D'):
635 case KEY_DELETE:
636 if ( cursor < len ) {
637 memmove(cmdline+cursor, cmdline+cursor+1, len-cursor);
638 len--;
639 redraw = 1;
641 break;
643 case KEY_CTRL('U'):
644 if ( len ) {
645 len = cursor = 0;
646 cmdline[len] = '\0';
647 redraw = 1;
649 break;
651 case KEY_CTRL('W'):
652 if ( cursor ) {
653 int prevcursor = cursor;
655 while ( cursor && my_isspace(cmdline[cursor-1]) )
656 cursor--;
658 while ( cursor && !my_isspace(cmdline[cursor-1]) )
659 cursor--;
661 memmove(cmdline+cursor, cmdline+prevcursor, len-prevcursor+1);
662 len -= (cursor-prevcursor);
663 redraw = 1;
665 break;
667 case KEY_LEFT:
668 case KEY_CTRL('B'):
669 if ( cursor ) {
670 cursor--;
671 redraw = 1;
673 break;
675 case KEY_RIGHT:
676 case KEY_CTRL('F'):
677 if ( cursor < len ) {
678 putchar(cmdline[cursor++]);
680 break;
682 case KEY_CTRL('K'):
683 if ( cursor < len ) {
684 cmdline[len = cursor] = '\0';
685 redraw = 1;
687 break;
689 case KEY_HOME:
690 case KEY_CTRL('A'):
691 if ( cursor ) {
692 cursor = 0;
693 redraw = 1;
695 break;
697 case KEY_END:
698 case KEY_CTRL('E'):
699 if ( cursor != len ) {
700 cursor = len;
701 redraw = 1;
703 break;
705 case KEY_F1:
706 case KEY_F2:
707 case KEY_F3:
708 case KEY_F4:
709 case KEY_F5:
710 case KEY_F6:
711 case KEY_F7:
712 case KEY_F8:
713 case KEY_F9:
714 case KEY_F10:
715 case KEY_F11:
716 case KEY_F12:
717 show_fkey(key);
718 redraw = 1;
719 break;
721 default:
722 if ( key >= ' ' && key <= 0xFF && len < MAX_CMDLINE_LEN-1 ) {
723 if ( cursor == len ) {
724 cmdline[len] = key;
725 cmdline[++len] = '\0';
726 cursor++;
727 putchar(key);
728 prev_len++;
729 } else {
730 memmove(cmdline+cursor+1, cmdline+cursor, len-cursor+1);
731 cmdline[cursor++] = key;
732 len++;
733 redraw = 1;
736 break;
741 static inline int
742 shift_is_held(void)
744 uint8_t shift_bits = *(uint8_t *)0x417;
746 return !!(shift_bits & 0x5d); /* Caps/Scroll/Alt/Shift */
749 static void
750 print_timeout_message(int tol, int row, const char *msg)
752 char buf[256];
753 int nc = 0, nnc;
754 const char *tp = msg;
755 char tc;
756 char *tq = buf;
758 while ((size_t)(tq-buf) < (sizeof buf-16) && (tc = *tp)) {
759 tp++;
760 if (tc == '#') {
761 nnc = sprintf(tq, "\2#15%d\2#14", tol);
762 tq += nnc;
763 nc += nnc-8; /* 8 formatting characters */
764 } else if (tc == '{') {
765 /* Deal with {singular[,dual],plural} constructs */
766 struct {
767 const char *s, *e;
768 } tx[3];
769 const char *tpp;
770 int n = 0;
772 memset(tx, 0, sizeof tx);
774 tx[0].s = tp;
776 while (*tp && *tp != '}') {
777 if (*tp == ',' && n < 2) {
778 tx[n].e = tp;
779 n++;
780 tx[n].s = tp+1;
782 tp++;
784 tx[n].e = tp;
786 if (*tp)
787 tp++; /* Skip final bracket */
789 if (!tx[1].s)
790 tx[1] = tx[0];
791 if (!tx[2].s)
792 tx[2] = tx[1];
794 /* Now [0] is singular, [1] is dual, and [2] is plural,
795 even if the user only specified some of them. */
797 switch (tol) {
798 case 1: n = 0; break;
799 case 2: n = 1; break;
800 default: n = 2; break;
803 for (tpp = tx[n].s; tpp < tx[n].e; tpp++) {
804 if ((size_t)(tq-buf) < (sizeof buf)) {
805 *tq++ = *tpp;
806 nc++;
809 } else {
810 *tq++ = tc;
811 nc++;
814 *tq = '\0';
816 /* Let's hope 4 spaces on each side is enough... */
817 printf("\033[%d;%dH\2#14 %s ", row, HSHIFT+1+((WIDTH-nc-8)>>1), buf);
820 static const char *
821 do_hidden_menu(void)
823 int key;
824 int timeout_left, this_timeout;
826 clear_screen();
828 if ( !setjmp(timeout_jump) ) {
829 timeout_left = timeout;
831 while (!timeout || timeout_left) {
832 int tol = timeout_left/CLK_TCK;
834 print_timeout_message(tol, HIDDEN_ROW, messages[MSG_AUTOBOOT].msg);
836 this_timeout = min(timeout_left, CLK_TCK);
837 key = mygetkey(this_timeout);
839 if (key != KEY_NONE)
840 return NULL; /* Key pressed */
842 timeout_left -= this_timeout;
846 return menu_entries[defentry].cmdline; /* Default entry */
849 static const char *
850 run_menu(void)
852 int key;
853 int done = 0;
854 volatile int entry = defentry, prev_entry = -1;
855 int top = 0, prev_top = -1;
856 int clear = 1, to_clear;
857 const char *cmdline = NULL;
858 volatile clock_t key_timeout, timeout_left, this_timeout;
860 /* Note: for both key_timeout and timeout == 0 means no limit */
861 timeout_left = key_timeout = timeout;
863 /* If we're in shiftkey mode, exit immediately unless a shift key is pressed */
864 if ( shiftkey && !shift_is_held() ) {
865 return menu_entries[defentry].cmdline;
868 /* Handle hiddenmenu */
869 if ( hiddenmenu ) {
870 cmdline = do_hidden_menu();
871 if (cmdline)
872 return cmdline;
874 /* Otherwise display the menu now; the timeout has already been
875 cancelled, since the user pressed a key. */
876 hiddenmenu = 0;
877 key_timeout = 0;
880 /* Handle both local and global timeout */
881 if ( setjmp(timeout_jump) ) {
882 entry = defentry;
884 if ( top < 0 || top < entry-MENU_ROWS+1 )
885 top = max(0, entry-MENU_ROWS+1);
886 else if ( top > entry || top > max(0,nentries-MENU_ROWS) )
887 top = min(entry, max(0,nentries-MENU_ROWS));
889 draw_menu(ontimeout ? -1 : entry, top, 1);
890 cmdline = ontimeout ? ontimeout : menu_entries[entry].cmdline;
891 done = 1;
894 while ( !done ) {
895 if ( entry < 0 )
896 entry = 0;
897 else if ( entry >= nentries )
898 entry = nentries-1;
900 if ( top < 0 || top < entry-MENU_ROWS+1 )
901 top = max(0, entry-MENU_ROWS+1);
902 else if ( top > entry || top > max(0,nentries-MENU_ROWS) )
903 top = min(entry, max(0,nentries-MENU_ROWS));
905 /* Start with a clear screen */
906 if ( clear ) {
907 /* Clear and redraw whole screen */
908 /* Enable ASCII on G0 and DEC VT on G1; do it in this order
909 to avoid confusing the Linux console */
910 clear_screen();
911 clear = 0;
912 prev_entry = prev_top = -1;
915 if ( top != prev_top ) {
916 draw_menu(entry, top, 1);
917 display_help(menu_entries[entry].helptext);
918 } else if ( entry != prev_entry ) {
919 draw_row(prev_entry-top+4+VSHIFT, entry, top, 0, 0);
920 draw_row(entry-top+4+VSHIFT, entry, top, 0, 0);
921 display_help(menu_entries[entry].helptext);
924 prev_entry = entry; prev_top = top;
926 /* Cursor movement cancels timeout */
927 if ( entry != defentry )
928 key_timeout = 0;
930 if ( key_timeout ) {
931 int tol = timeout_left/CLK_TCK;
932 print_timeout_message(tol, TIMEOUT_ROW, messages[MSG_AUTOBOOT].msg);
933 to_clear = 1;
934 } else {
935 to_clear = 0;
938 this_timeout = min(min(key_timeout, timeout_left), CLK_TCK);
939 key = mygetkey(this_timeout);
941 if ( key != KEY_NONE ) {
942 timeout_left = key_timeout;
943 if ( to_clear )
944 printf("\033[%d;1H\1#0\033[K", TIMEOUT_ROW);
947 switch ( key ) {
948 case KEY_NONE: /* Timeout */
949 /* This is somewhat hacky, but this at least lets the user
950 know what's going on, and still deals with "phantom inputs"
951 e.g. on serial ports.
953 Warning: a timeout will boot the default entry without any
954 password! */
955 if ( key_timeout ) {
956 if ( timeout_left <= this_timeout )
957 longjmp(timeout_jump, 1);
959 timeout_left -= this_timeout;
961 break;
963 case KEY_CTRL('L'):
964 clear = 1;
965 break;
967 case KEY_ENTER:
968 case KEY_CTRL('J'):
969 key_timeout = 0; /* Cancels timeout */
970 if ( menu_entries[entry].passwd ) {
971 clear = 1;
972 done = ask_passwd(menu_entries[entry].passwd);
973 } else {
974 done = 1;
976 cmdline = menu_entries[entry].cmdline;
977 break;
979 case KEY_UP:
980 case KEY_CTRL('P'):
981 if ( entry > 0 ) {
982 entry--;
983 if ( entry < top )
984 top -= MENU_ROWS;
986 break;
988 case KEY_DOWN:
989 case KEY_CTRL('N'):
990 if ( entry < nentries-1 ) {
991 entry++;
992 if ( entry >= top+MENU_ROWS )
993 top += MENU_ROWS;
995 break;
997 case KEY_PGUP:
998 case KEY_LEFT:
999 case KEY_CTRL('B'):
1000 case '<':
1001 entry -= MENU_ROWS;
1002 top -= MENU_ROWS;
1003 break;
1005 case KEY_PGDN:
1006 case KEY_RIGHT:
1007 case KEY_CTRL('F'):
1008 case '>':
1009 case ' ':
1010 entry += MENU_ROWS;
1011 top += MENU_ROWS;
1012 break;
1014 case '-':
1015 entry--;
1016 top--;
1017 break;
1019 case '+':
1020 entry++;
1021 top++;
1022 break;
1024 case KEY_CTRL('A'):
1025 case KEY_HOME:
1026 top = entry = 0;
1027 break;
1029 case KEY_CTRL('E'):
1030 case KEY_END:
1031 entry = nentries - 1;
1032 top = max(0, nentries-MENU_ROWS);
1033 break;
1035 case KEY_F1:
1036 case KEY_F2:
1037 case KEY_F3:
1038 case KEY_F4:
1039 case KEY_F5:
1040 case KEY_F6:
1041 case KEY_F7:
1042 case KEY_F8:
1043 case KEY_F9:
1044 case KEY_F10:
1045 case KEY_F11:
1046 case KEY_F12:
1047 show_fkey(key);
1048 clear = 1;
1049 break;
1051 case KEY_TAB:
1052 if ( allowedit ) {
1053 int ok = 1;
1055 key_timeout = 0; /* Cancels timeout */
1056 draw_row(entry-top+4+VSHIFT, -1, top, 0, 0);
1058 if ( menu_master_passwd ) {
1059 ok = ask_passwd(NULL);
1060 clear_screen();
1061 draw_menu(-1, top, 0);
1062 } else {
1063 /* Erase [Tab] message and help text*/
1064 printf("\033[%d;1H\1#0\033[K", TABMSG_ROW);
1065 display_help(NULL);
1068 if ( ok ) {
1069 cmdline = edit_cmdline(menu_entries[entry].cmdline, top);
1070 done = !!cmdline;
1071 clear = 1; /* In case we hit [Esc] and done is null */
1072 } else {
1073 draw_row(entry-top+4+VSHIFT, entry, top, 0, 0);
1076 break;
1077 case KEY_CTRL('C'): /* Ctrl-C */
1078 case KEY_ESC: /* Esc */
1079 if ( allowedit ) {
1080 done = 1;
1081 clear = 1;
1082 key_timeout = 0;
1084 draw_row(entry-top+4+VSHIFT, -1, top, 0, 0);
1086 if ( menu_master_passwd )
1087 done = ask_passwd(NULL);
1089 break;
1090 default:
1091 if ( key > 0 && key < 0xFF ) {
1092 key &= ~0x20; /* Upper case */
1093 if ( menu_hotkeys[key] ) {
1094 key_timeout = 0;
1095 entry = menu_hotkeys[key] - menu_entries;
1096 /* Should we commit at this point? */
1099 break;
1103 printf("\033[?25h"); /* Show cursor */
1105 /* Return the label name so localboot and ipappend work */
1106 return cmdline;
1110 static void
1111 execute(const char *cmdline, enum kernel_type type)
1113 com32sys_t ireg;
1114 const char *p, **pp;
1115 char *q = __com32.cs_bounce;
1116 const char *kernel, *args;
1118 memset(&ireg, 0, sizeof ireg);
1120 kernel = q;
1121 p = cmdline;
1122 while ( *p && !my_isspace(*p) ) {
1123 *q++ = *p++;
1125 *q++ = '\0';
1127 args = q;
1128 while ( *p && my_isspace(*p) )
1129 p++;
1131 strcpy(q, p);
1133 if (kernel[0] == '.' && type == KT_NONE) {
1134 /* It might be a type specifier */
1135 enum kernel_type type = KT_NONE;
1136 for (pp = kernel_types; *pp; pp++, type++) {
1137 if (!strcmp(kernel+1, *pp)) {
1138 execute(p, type); /* Strip the type specifier and retry */
1143 if (type == KT_LOCALBOOT) {
1144 ireg.eax.w[0] = 0x0014; /* Local boot */
1145 ireg.edx.w[0] = strtoul(kernel, NULL, 0);
1146 } else {
1147 if (type < KT_KERNEL)
1148 type = KT_KERNEL;
1150 ireg.eax.w[0] = 0x0016; /* Run kernel image */
1151 ireg.esi.w[0] = OFFS(kernel);
1152 ireg.ds = SEG(kernel);
1153 ireg.ebx.w[0] = OFFS(args);
1154 ireg.es = SEG(args);
1155 ireg.edx.l = type-KT_KERNEL;
1156 /* ireg.ecx.l = 0; */ /* We do ipappend "manually" */
1159 __intcall(0x22, &ireg, NULL);
1161 /* If this returns, something went bad; return to menu */
1164 int menu_main(int argc, char *argv[])
1166 const char *cmdline;
1167 int rows, cols;
1168 int i;
1170 (void)argc;
1172 console_prepare();
1174 install_default_color_table();
1175 if (getscreensize(1, &rows, &cols)) {
1176 /* Unknown screen size? */
1177 rows = 24;
1178 cols = 80;
1181 WIDTH = cols;
1182 parse_configs(argv+1);
1184 /* If anyone has specified negative parameters, consider them
1185 relative to the bottom row of the screen. */
1186 for (i = 0; mparm[i].name; i++)
1187 if (mparm[i].value < 0)
1188 mparm[i].value = max(mparm[i].value+rows, 0);
1190 draw_background(menu_background);
1192 if ( !nentries ) {
1193 fputs("No LABEL entries found in configuration file!\n", stdout);
1194 return 1; /* Error! */
1197 for(;;) {
1198 cmdline = run_menu();
1200 printf("\033[?25h\033[%d;1H\033[0m", END_ROW);
1201 console_cleanup();
1203 if ( cmdline ) {
1204 execute(cmdline, KT_NONE);
1205 if ( onerror )
1206 execute(onerror, KT_NONE);
1207 } else {
1208 return 0; /* Exit */
1211 console_prepare(); /* If we're looping... */