1 /* ----------------------------------------------------------------------- *
3 * Copyright 2004-2008 H. Peter Anvin - All Rights Reserved
4 * Copyright 2009-2011 Intel Corporation; author: H. Peter Anvin
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
9 * Boston MA 02110-1301, USA; either version 2 of the License, or
10 * (at your option) any later version; incorporated herein by reference.
12 * ----------------------------------------------------------------------- */
17 * Simple menu system which displays a list and allows the user to select
18 * a command line and/or edit it.
31 #include <syslinux/adv.h>
35 /* The symbol "cm" always refers to the current menu across this file... */
36 static struct menu *cm;
38 const struct menu_parameter mparm[NPARAMS] = {
39 [P_WIDTH] = {"width", 0},
40 [P_MARGIN] = {"margin", 10},
41 [P_PASSWD_MARGIN] = {"passwordmargin", 3},
42 [P_MENU_ROWS] = {"rows", 12},
43 [P_TABMSG_ROW] = {"tabmsgrow", 18},
44 [P_CMDLINE_ROW] = {"cmdlinerow", 18},
45 [P_END_ROW] = {"endrow", -1},
46 [P_PASSWD_ROW] = {"passwordrow", 11},
47 [P_TIMEOUT_ROW] = {"timeoutrow", 20},
48 [P_HELPMSG_ROW] = {"helpmsgrow", 22},
49 [P_HELPMSGEND_ROW] = {"helpmsgendrow", -1},
50 [P_HSHIFT] = {"hshift", 0},
51 [P_VSHIFT] = {"vshift", 0},
52 [P_HIDDEN_ROW] = {"hiddenrow", -2},
55 /* These macros assume "cm" is a pointer to the current menu */
56 #define WIDTH (cm->mparm[P_WIDTH])
57 #define MARGIN (cm->mparm[P_MARGIN])
58 #define PASSWD_MARGIN (cm->mparm[P_PASSWD_MARGIN])
59 #define MENU_ROWS (cm->mparm[P_MENU_ROWS])
60 #define TABMSG_ROW (cm->mparm[P_TABMSG_ROW]+VSHIFT)
61 #define CMDLINE_ROW (cm->mparm[P_CMDLINE_ROW]+VSHIFT)
62 #define END_ROW (cm->mparm[P_END_ROW])
63 #define PASSWD_ROW (cm->mparm[P_PASSWD_ROW]+VSHIFT)
64 #define TIMEOUT_ROW (cm->mparm[P_TIMEOUT_ROW]+VSHIFT)
65 #define HELPMSG_ROW (cm->mparm[P_HELPMSG_ROW]+VSHIFT)
66 #define HELPMSGEND_ROW (cm->mparm[P_HELPMSGEND_ROW])
67 #define HSHIFT (cm->mparm[P_HSHIFT])
68 #define VSHIFT (cm->mparm[P_VSHIFT])
69 #define HIDDEN_ROW (cm->mparm[P_HIDDEN_ROW])
71 static char *pad_line(const char *text, int align, int width)
73 static char buffer[MAX_CMDLINE_LEN];
76 if (width >= (int)sizeof buffer)
77 return NULL; /* Can't do it */
83 memset(buffer, ' ', width);
85 p = ((width - n) * align) >> 1;
86 memcpy(buffer + p, text, n);
91 /* Display an entry, with possible hotkey highlight. Assumes
92 that the current attribute is the non-hotkey one, and will
93 guarantee that as an exit condition as well. */
95 display_entry(const struct menu_entry *entry, const char *attrib,
96 const char *hotattrib, int width)
98 const char *p = entry->displayname;
104 switch (entry->action) {
123 if (*p && ((unsigned char)*p & ~0x20) == entry->hotkey) {
124 fputs(hotattrib, stdout);
126 fputs(attrib, stdout);
145 static void draw_row(int y, int sel, int top, int sbtop, int sbbot)
147 int i = (y - 4 - VSHIFT) + top;
148 int dis = (i < cm->nentries) && is_disabled(cm->menu_entries[i]);
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");
154 if (i >= cm->nentries) {
155 fputs(pad_line("", 0, WIDTH - 2 * MARGIN - 4), stdout);
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);
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");
169 printf(" \1#1\016x\017");
171 putchar(' '); /* Don't modify the scrollbar */
175 static jmp_buf timeout_jump;
177 int mygetkey(clock_t timeout)
184 return get_key(stdin, timeout);
187 tto = min(totaltimeout, INT_MAX);
188 to = timeout ? min(tto, timeout) : tto;
191 key = get_key(stdin, to);
192 t = times(NULL) - t0;
194 if (totaltimeout <= t)
195 longjmp(timeout_jump, 1);
211 static int ask_passwd(const char *menu_entry)
213 char user_passwd[WIDTH], *p;
219 printf("\033[%d;%dH\2#11\016l", PASSWD_ROW, PASSWD_MARGIN + 1);
220 for (x = 2; x <= WIDTH - 2 * PASSWD_MARGIN - 1; x++)
223 printf("k\033[%d;%dHx", PASSWD_ROW + 1, PASSWD_MARGIN + 1);
224 for (x = 2; x <= WIDTH - 2 * PASSWD_MARGIN - 1; x++)
227 printf("x\033[%d;%dHm", PASSWD_ROW + 2, PASSWD_MARGIN + 1);
228 for (x = 2; x <= WIDTH - 2 * PASSWD_MARGIN - 1; x++)
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);
237 /* Actually allow user to type a password, then compare to the SHA1 */
252 p = user_passwd; /* No password entered */
259 if (p > user_passwd) {
266 while (p > user_passwd) {
273 if (key >= ' ' && key <= 0xFF &&
274 (p - user_passwd) < WIDTH - 2 * PASSWD_MARGIN - 5) {
282 if (p == user_passwd)
283 return 0; /* No password entered */
287 rv = (cm->menu_master_passwd &&
288 passwd_compare(cm->menu_master_passwd, user_passwd))
289 || (menu_entry && passwd_compare(menu_entry, user_passwd));
292 memset(user_passwd, 0, WIDTH);
298 static void draw_menu(int sel, int top, int edit_line)
301 int sbtop = 0, sbbot = 0;
305 if (cm->nentries > MENU_ROWS) {
306 int sblen = max(MENU_ROWS * MENU_ROWS / cm->nentries, 1);
307 sbtop = (MENU_ROWS - sblen + 1) * top / (cm->nentries - MENU_ROWS + 1);
308 sbbot = sbtop + sblen - 1;
310 sbbot += 4; /* Starting row of scrollbar */
313 printf("\033[%d;%dH\1#1\016l", VSHIFT + 1, HSHIFT + MARGIN + 1);
314 for (x = 2 + HSHIFT; x <= (WIDTH - 2 * MARGIN - 1) + HSHIFT; x++)
317 printf("k\033[%d;%dH\1#1x\017\1#2 %s \1#1\016x",
319 HSHIFT + MARGIN + 1, pad_line(cm->title, 1, WIDTH - 2 * MARGIN - 4));
321 printf("\033[%d;%dH\1#1t", VSHIFT + 3, HSHIFT + MARGIN + 1);
322 for (x = 2 + HSHIFT; x <= (WIDTH - 2 * MARGIN - 1) + HSHIFT; x++)
324 fputs("u\017", stdout);
326 for (y = 4 + VSHIFT; y < 4 + VSHIFT + MENU_ROWS; y++)
327 draw_row(y, sel, top, sbtop, sbbot);
329 printf("\033[%d;%dH\1#1\016m", y, HSHIFT + MARGIN + 1);
330 for (x = 2 + HSHIFT; x <= (WIDTH - 2 * MARGIN - 1) + HSHIFT; x++)
332 fputs("j\017", stdout);
334 if (edit_line && cm->allowedit && !cm->menu_master_passwd)
335 tabmsg = cm->messages[MSG_TAB];
337 tabmsg = cm->messages[MSG_NOTAB];
339 tabmsg_len = strlen(tabmsg);
341 printf("\1#8\033[%d;%dH%s",
342 TABMSG_ROW, 1 + HSHIFT + ((WIDTH - tabmsg_len) >> 1), tabmsg);
343 printf("\1#0\033[%d;1H", END_ROW);
346 static void clear_screen(void)
348 fputs("\033e\033%@\033)0\033(B\1#0\033[?25l\033[2J", stdout);
351 static void display_help(const char *text)
358 printf("\1#0\033[%d;1H", HELPMSG_ROW);
360 printf("\2#16\033[%d;1H", HELPMSG_ROW);
363 for (p = text, row = HELPMSG_ROW; *p && row <= HELPMSGEND_ROW; p++) {
371 printf("\033[K\033[%d;1H", ++row);
378 fputs("\033[K", stdout);
380 while (row <= HELPMSGEND_ROW) {
381 printf("\033[K\033[%d;1H", ++row);
385 static void show_fkey(int key)
435 if (cm->fkeyhelp[fkey].textname)
436 key = show_message_file(cm->fkeyhelp[fkey].textname,
437 cm->fkeyhelp[fkey].background);
443 static const char *edit_cmdline(const char *input, int top)
445 static char cmdline[MAX_CMDLINE_LEN];
446 int key, len, prev_len, cursor;
447 int redraw = 1; /* We enter with the menu already drawn */
449 strlcpy(cmdline, input, MAX_CMDLINE_LEN);
450 cmdline[MAX_CMDLINE_LEN - 1] = '\0';
452 len = cursor = strlen(cmdline);
457 /* Clear and redraw whole screen */
458 /* Enable ASCII on G0 and DEC VT on G1; do it in this order
459 to avoid confusing the Linux console */
461 draw_menu(-1, top, 1);
466 /* Redraw the command line */
467 printf("\033[?25l\033[%d;1H\1#9> \2#10%s",
468 CMDLINE_ROW, pad_line(cmdline, 0, max(len, prev_len)));
469 printf("\2#10\033[%d;3H%s\033[?25h",
470 CMDLINE_ROW, pad_line(cmdline, 0, cursor));
493 memmove(cmdline + cursor - 1, cmdline + cursor,
504 memmove(cmdline + cursor, cmdline + cursor + 1, len - cursor);
520 int prevcursor = cursor;
522 while (cursor && my_isspace(cmdline[cursor - 1]))
525 while (cursor && !my_isspace(cmdline[cursor - 1]))
528 memmove(cmdline + cursor, cmdline + prevcursor,
529 len - prevcursor + 1);
530 len -= (prevcursor - cursor);
546 putchar(cmdline[cursor++]);
552 cmdline[len = cursor] = '\0';
590 if (key >= ' ' && key <= 0xFF && len < MAX_CMDLINE_LEN - 1) {
593 cmdline[++len] = '\0';
598 memmove(cmdline + cursor + 1, cmdline + cursor,
600 cmdline[cursor++] = key;
610 static inline int shift_is_held(void)
612 uint8_t shift_bits = *(uint8_t *) 0x417;
614 return !!(shift_bits & 0x5d); /* Caps/Scroll/Alt/Shift */
617 static void print_timeout_message(int tol, int row, const char *msg)
619 static int last_msg_len = 0;
621 int nc = 0, nnc, padc;
622 const char *tp = msg;
626 while ((size_t) (tq - buf) < (sizeof buf - 16) && (tc = *tp)) {
629 nnc = sprintf(tq, "\2#15%d\2#14", tol);
631 nc += nnc - 8; /* 8 formatting characters */
632 } else if (tc == '{') {
633 /* Deal with {singular[,dual],plural} constructs */
640 memset(tx, 0, sizeof tx);
644 while (*tp && *tp != '}') {
645 if (*tp == ',' && n < 2) {
655 tp++; /* Skip final bracket */
662 /* Now [0] is singular, [1] is dual, and [2] is plural,
663 even if the user only specified some of them. */
677 for (tpp = tx[n].s; tpp < tx[n].e; tpp++) {
678 if ((size_t) (tq - buf) < (sizeof buf)) {
690 if (nc >= last_msg_len) {
693 padc = (last_msg_len - nc + 1) >> 1;
696 printf("\033[%d;%dH\2#14%*s%s%*s", row,
697 HSHIFT + 1 + ((WIDTH - nc) >> 1) - padc,
698 padc, "", buf, padc, "");
703 /* Set the background screen, etc. */
704 static void prepare_screen_for_menu(void)
706 console_color_table = cm->color_table;
707 console_color_table_size = menu_color_table_size;
708 set_background(cm->menu_background);
711 static const char *do_hidden_menu(void)
714 int timeout_left, this_timeout;
718 if (!setjmp(timeout_jump)) {
719 timeout_left = cm->timeout;
721 while (!cm->timeout || timeout_left) {
722 int tol = timeout_left / CLK_TCK;
724 print_timeout_message(tol, HIDDEN_ROW, cm->messages[MSG_AUTOBOOT]);
726 this_timeout = min(timeout_left, CLK_TCK);
727 key = mygetkey(this_timeout);
729 if (key != KEY_NONE) {
730 /* Clear the message from the screen */
731 print_timeout_message(0, HIDDEN_ROW, "");
732 return hide_key[key]; /* NULL if no MENU HIDEKEY in effect */
735 timeout_left -= this_timeout;
739 /* Clear the message from the screen */
740 print_timeout_message(0, HIDDEN_ROW, "");
743 return cm->ontimeout;
745 return cm->menu_entries[cm->defentry]->cmdline; /* Default entry */
748 static const char *run_menu(void)
752 volatile int entry = cm->curentry;
754 volatile int top = cm->curtop;
756 int clear = 1, to_clear;
757 const char *cmdline = NULL;
758 volatile clock_t key_timeout, timeout_left, this_timeout;
759 const struct menu_entry *me;
762 /* Note: for both key_timeout and timeout == 0 means no limit */
763 timeout_left = key_timeout = cm->timeout;
765 /* If we're in shiftkey mode, exit immediately unless a shift key
767 if (shiftkey && !shift_is_held()) {
768 return cm->menu_entries[cm->defentry]->cmdline;
773 /* Do this before hiddenmenu handling, so we show the background */
774 prepare_screen_for_menu();
776 /* Handle hiddenmenu */
778 cmdline = do_hidden_menu();
782 /* Otherwise display the menu now; the timeout has already been
783 cancelled, since the user pressed a key. */
788 /* Handle both local and global timeout */
789 if (setjmp(timeout_jump)) {
790 entry = cm->defentry;
792 if (top < 0 || top < entry - MENU_ROWS + 1)
793 top = max(0, entry - MENU_ROWS + 1);
794 else if (top > entry || top > max(0, cm->nentries - MENU_ROWS))
795 top = min(entry, max(0, cm->nentries - MENU_ROWS));
797 draw_menu(cm->ontimeout ? -1 : entry, top, 1);
799 cm->ontimeout ? cm->ontimeout : cm->menu_entries[entry]->cmdline;
806 while (entry < cm->nentries && is_disabled(cm->menu_entries[entry]))
809 if (entry >= cm->nentries) {
810 entry = cm->nentries - 1;
811 while (entry > 0 && is_disabled(cm->menu_entries[entry]))
815 me = cm->menu_entries[entry];
817 if (top < 0 || top < entry - MENU_ROWS + 1)
818 top = max(0, entry - MENU_ROWS + 1);
819 else if (top > entry || top > max(0, cm->nentries - MENU_ROWS))
820 top = min(entry, max(0, cm->nentries - MENU_ROWS));
822 /* Start with a clear screen */
824 /* Clear and redraw whole screen */
825 /* Enable ASCII on G0 and DEC VT on G1; do it in this order
826 to avoid confusing the Linux console */
828 prepare_screen_for_menu();
831 prev_entry = prev_top = -1;
834 if (top != prev_top) {
835 draw_menu(entry, top, 1);
836 display_help(me->helptext);
837 } else if (entry != prev_entry) {
838 draw_row(prev_entry - top + 4 + VSHIFT, entry, top, 0, 0);
839 draw_row(entry - top + 4 + VSHIFT, entry, top, 0, 0);
840 display_help(me->helptext);
845 cm->curentry = entry;
848 /* Cursor movement cancels timeout */
849 if (entry != cm->defentry)
853 int tol = timeout_left / CLK_TCK;
854 print_timeout_message(tol, TIMEOUT_ROW, cm->messages[MSG_AUTOBOOT]);
860 if (hotkey && me->immediate) {
861 /* If the hotkey was flagged immediate, simulate pressing ENTER */
864 this_timeout = min(min(key_timeout, timeout_left),
866 key = mygetkey(this_timeout);
868 if (key != KEY_NONE) {
869 timeout_left = key_timeout;
871 printf("\033[%d;1H\1#0\033[K", TIMEOUT_ROW);
878 case KEY_NONE: /* Timeout */
879 /* This is somewhat hacky, but this at least lets the user
880 know what's going on, and still deals with "phantom inputs"
881 e.g. on serial ports.
883 Warning: a timeout will boot the default entry without any
886 if (timeout_left <= this_timeout)
887 longjmp(timeout_jump, 1);
889 timeout_left -= this_timeout;
899 key_timeout = 0; /* Cancels timeout */
902 done = ask_passwd(me->passwd);
908 switch (me->action) {
910 cmdline = me->cmdline;
918 entry = cm->curentry;
922 /* Quit menu system */
925 draw_row(entry - top + 4 + VSHIFT, -1, top, 0, 0);
928 key = show_message_file(me->cmdline, me->background);
929 /* If the exit was an F-key, display that help screen */
939 if (done && !me->passwd) {
940 /* Only save a new default if we don't have a password... */
941 if (me->save && me->label) {
942 syslinux_setadv(ADV_MENUSAVE, strlen(me->label), me->label);
943 syslinux_adv_write();
954 if (!is_disabled(cm->menu_entries[entry]))
961 while (++entry < cm->nentries) {
962 if (entry >= top + MENU_ROWS)
964 if (!is_disabled(cm->menu_entries[entry]))
975 while (entry > 0 && is_disabled(cm->menu_entries[entry])) {
989 while (entry < cm->nentries - 1
990 && is_disabled(cm->menu_entries[entry])) {
992 if (entry >= top + MENU_ROWS)
1001 if (!is_disabled(cm->menu_entries[entry]))
1007 while (entry < cm->nentries - 1) {
1010 if (!is_disabled(cm->menu_entries[entry]))
1022 entry = cm->nentries - 1;
1023 top = max(0, cm->nentries - MENU_ROWS);
1043 if (cm->allowedit && me->action == MA_CMD) {
1046 key_timeout = 0; /* Cancels timeout */
1047 draw_row(entry - top + 4 + VSHIFT, -1, top, 0, 0);
1049 if (cm->menu_master_passwd) {
1050 ok = ask_passwd(NULL);
1052 draw_menu(-1, top, 0);
1054 /* Erase [Tab] message and help text */
1055 printf("\033[%d;1H\1#0\033[K", TABMSG_ROW);
1060 cmdline = edit_cmdline(me->cmdline, top);
1062 clear = 1; /* In case we hit [Esc] and done is null */
1064 draw_row(entry - top + 4 + VSHIFT, entry, top, 0, 0);
1068 case KEY_CTRL('C'): /* Ctrl-C */
1069 case KEY_ESC: /* Esc */
1073 entry = cm->curentry;
1075 } else if (cm->allowedit) {
1080 draw_row(entry - top + 4 + VSHIFT, -1, top, 0, 0);
1082 if (cm->menu_master_passwd)
1083 done = ask_passwd(NULL);
1087 if (key > 0 && key < 0xFF) {
1088 key &= ~0x20; /* Upper case */
1089 if (cm->menu_hotkeys[key]) {
1091 entry = cm->menu_hotkeys[key]->entry;
1092 /* Should we commit at this point? */
1100 printf("\033[?25h"); /* Show cursor */
1102 /* Return the label name so localboot and ipappend work */
1106 int main(int argc, char *argv[])
1108 const char *cmdline;
1115 parse_configs(argv + 1);
1118 * We don't start the console until we have parsed the configuration
1119 * file, since the configuration file might impact the console
1120 * configuration, e.g. MENU RESOLUTION.
1123 if (getscreensize(1, &rows, &cols)) {
1124 /* Unknown screen size? */
1129 /* Some postprocessing for all menus */
1130 for (m = menu_list; m; m = m->next) {
1131 if (!m->mparm[P_WIDTH])
1132 m->mparm[P_WIDTH] = cols;
1134 /* If anyone has specified negative parameters, consider them
1135 relative to the bottom row of the screen. */
1136 for (i = 0; i < NPARAMS; i++)
1137 if (m->mparm[i] < 0)
1138 m->mparm[i] = max(m->mparm[i] + rows, 0);
1143 if (!cm->nentries) {
1144 fputs("Initial menu has no LABEL entries!\n", stdout);
1145 return 1; /* Error! */
1149 local_cursor_enable(true);
1150 cmdline = run_menu();
1155 local_cursor_enable(false);
1156 printf("\033[?25h\033[%d;1H\033[0m", END_ROW);
1159 execute(cmdline, KT_NONE);
1161 execute(cm->onerror, KT_NONE);
1163 return 0; /* Exit */