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.
32 #include <syslinux/adv.h>
33 #include <syslinux/boot.h>
37 /* The symbol "cm" always refers to the current menu across this file... */
38 static struct menu
*cm
;
40 const struct menu_parameter mparm
[NPARAMS
] = {
41 [P_WIDTH
] = {"width", 0},
42 [P_MARGIN
] = {"margin", 10},
43 [P_PASSWD_MARGIN
] = {"passwordmargin", 3},
44 [P_MENU_ROWS
] = {"rows", 12},
45 [P_TABMSG_ROW
] = {"tabmsgrow", 18},
46 [P_CMDLINE_ROW
] = {"cmdlinerow", 18},
47 [P_END_ROW
] = {"endrow", -1},
48 [P_PASSWD_ROW
] = {"passwordrow", 11},
49 [P_TIMEOUT_ROW
] = {"timeoutrow", 20},
50 [P_HELPMSG_ROW
] = {"helpmsgrow", 22},
51 [P_HELPMSGEND_ROW
] = {"helpmsgendrow", -1},
52 [P_HSHIFT
] = {"hshift", 0},
53 [P_VSHIFT
] = {"vshift", 0},
54 [P_HIDDEN_ROW
] = {"hiddenrow", -2},
57 /* These macros assume "cm" is a pointer to the current menu */
58 #define WIDTH (cm->mparm[P_WIDTH])
59 #define MARGIN (cm->mparm[P_MARGIN])
60 #define PASSWD_MARGIN (cm->mparm[P_PASSWD_MARGIN])
61 #define MENU_ROWS (cm->mparm[P_MENU_ROWS])
62 #define TABMSG_ROW (cm->mparm[P_TABMSG_ROW]+VSHIFT)
63 #define CMDLINE_ROW (cm->mparm[P_CMDLINE_ROW]+VSHIFT)
64 #define END_ROW (cm->mparm[P_END_ROW])
65 #define PASSWD_ROW (cm->mparm[P_PASSWD_ROW]+VSHIFT)
66 #define TIMEOUT_ROW (cm->mparm[P_TIMEOUT_ROW]+VSHIFT)
67 #define HELPMSG_ROW (cm->mparm[P_HELPMSG_ROW]+VSHIFT)
68 #define HELPMSGEND_ROW (cm->mparm[P_HELPMSGEND_ROW])
69 #define HSHIFT (cm->mparm[P_HSHIFT])
70 #define VSHIFT (cm->mparm[P_VSHIFT])
71 #define HIDDEN_ROW (cm->mparm[P_HIDDEN_ROW])
73 static char *pad_line(const char *text
, int align
, int width
)
75 static char buffer
[MAX_CMDLINE_LEN
];
78 if (width
>= (int)sizeof buffer
)
79 return NULL
; /* Can't do it */
85 memset(buffer
, ' ', width
);
87 p
= ((width
- n
) * align
) >> 1;
88 memcpy(buffer
+ p
, text
, n
);
93 /* Display an entry, with possible hotkey highlight. Assumes
94 that the current attribute is the non-hotkey one, and will
95 guarantee that as an exit condition as well. */
97 display_entry(const struct menu_entry
*entry
, const char *attrib
,
98 const char *hotattrib
, int width
)
100 const char *p
= entry
->displayname
;
106 switch (entry
->action
) {
125 if (*p
&& ((unsigned char)*p
& ~0x20) == entry
->hotkey
) {
126 fputs(hotattrib
, stdout
);
128 fputs(attrib
, stdout
);
147 static void draw_row(int y
, int sel
, int top
, int sbtop
, int sbbot
)
149 int i
= (y
- 4 - VSHIFT
) + top
;
150 int dis
= (i
< cm
->nentries
) && is_disabled(cm
->menu_entries
[i
]);
152 printf("\033[%d;%dH\1#1\016x\017%s ",
153 y
, MARGIN
+ 1 + HSHIFT
,
154 (i
== sel
) ? "\1#5" : dis
? "\2#17" : "\1#3");
156 if (i
>= cm
->nentries
) {
157 fputs(pad_line("", 0, WIDTH
- 2 * MARGIN
- 4), stdout
);
159 display_entry(cm
->menu_entries
[i
],
160 (i
== sel
) ? "\1#5" : dis
? "\2#17" : "\1#3",
161 (i
== sel
) ? "\1#6" : dis
? "\2#17" : "\1#4",
162 WIDTH
- 2 * MARGIN
- 4);
165 if (cm
->nentries
<= MENU_ROWS
) {
166 printf(" \1#1\016x\017");
167 } else if (sbtop
> 0) {
168 if (y
>= sbtop
&& y
<= sbbot
)
169 printf(" \1#7\016a\017");
171 printf(" \1#1\016x\017");
173 putchar(' '); /* Don't modify the scrollbar */
177 static jmp_buf timeout_jump
;
179 int mygetkey(clock_t timeout
)
186 return get_key(stdin
, timeout
);
189 tto
= min(totaltimeout
, INT_MAX
);
190 to
= timeout
? min(tto
, timeout
) : tto
;
193 key
= get_key(stdin
, to
);
194 t
= times(NULL
) - t0
;
196 if (totaltimeout
<= t
)
197 longjmp(timeout_jump
, 1);
213 static int ask_passwd(const char *menu_entry
)
215 char user_passwd
[WIDTH
], *p
;
221 printf("\033[%d;%dH\2#11\016l", PASSWD_ROW
, PASSWD_MARGIN
+ 1);
222 for (x
= 2; x
<= WIDTH
- 2 * PASSWD_MARGIN
- 1; x
++)
225 printf("k\033[%d;%dHx", PASSWD_ROW
+ 1, PASSWD_MARGIN
+ 1);
226 for (x
= 2; x
<= WIDTH
- 2 * PASSWD_MARGIN
- 1; x
++)
229 printf("x\033[%d;%dHm", PASSWD_ROW
+ 2, PASSWD_MARGIN
+ 1);
230 for (x
= 2; x
<= WIDTH
- 2 * PASSWD_MARGIN
- 1; x
++)
233 printf("j\017\033[%d;%dH\2#12 %s \033[%d;%dH\2#13",
234 PASSWD_ROW
, (WIDTH
- (strlen(cm
->messages
[MSG_PASSPROMPT
]) + 2)) / 2,
235 cm
->messages
[MSG_PASSPROMPT
], PASSWD_ROW
+ 1, PASSWD_MARGIN
+ 3);
239 /* Actually allow user to type a password, then compare to the SHA1 */
254 p
= user_passwd
; /* No password entered */
261 if (p
> user_passwd
) {
268 while (p
> user_passwd
) {
275 if (key
>= ' ' && key
<= 0xFF &&
276 (p
- user_passwd
) < WIDTH
- 2 * PASSWD_MARGIN
- 5) {
284 if (p
== user_passwd
)
285 return 0; /* No password entered */
289 rv
= (cm
->menu_master_passwd
&&
290 passwd_compare(cm
->menu_master_passwd
, user_passwd
))
291 || (menu_entry
&& passwd_compare(menu_entry
, user_passwd
));
294 memset(user_passwd
, 0, WIDTH
);
300 static void draw_menu(int sel
, int top
, int edit_line
)
303 int sbtop
= 0, sbbot
= 0;
307 if (cm
->nentries
> MENU_ROWS
) {
308 int sblen
= max(MENU_ROWS
* MENU_ROWS
/ cm
->nentries
, 1);
309 sbtop
= (MENU_ROWS
- sblen
+ 1) * top
/ (cm
->nentries
- MENU_ROWS
+ 1);
310 sbbot
= sbtop
+ sblen
- 1;
312 sbbot
+= 4; /* Starting row of scrollbar */
315 printf("\033[%d;%dH\1#1\016l", VSHIFT
+ 1, HSHIFT
+ MARGIN
+ 1);
316 for (x
= 2 + HSHIFT
; x
<= (WIDTH
- 2 * MARGIN
- 1) + HSHIFT
; x
++)
319 printf("k\033[%d;%dH\1#1x\017\1#2 %s \1#1\016x",
321 HSHIFT
+ MARGIN
+ 1, pad_line(cm
->title
, 1, WIDTH
- 2 * MARGIN
- 4));
323 printf("\033[%d;%dH\1#1t", VSHIFT
+ 3, HSHIFT
+ MARGIN
+ 1);
324 for (x
= 2 + HSHIFT
; x
<= (WIDTH
- 2 * MARGIN
- 1) + HSHIFT
; x
++)
326 fputs("u\017", stdout
);
328 for (y
= 4 + VSHIFT
; y
< 4 + VSHIFT
+ MENU_ROWS
; y
++)
329 draw_row(y
, sel
, top
, sbtop
, sbbot
);
331 printf("\033[%d;%dH\1#1\016m", y
, HSHIFT
+ MARGIN
+ 1);
332 for (x
= 2 + HSHIFT
; x
<= (WIDTH
- 2 * MARGIN
- 1) + HSHIFT
; x
++)
334 fputs("j\017", stdout
);
336 if (edit_line
&& cm
->allowedit
&& !cm
->menu_master_passwd
)
337 tabmsg
= cm
->messages
[MSG_TAB
];
339 tabmsg
= cm
->messages
[MSG_NOTAB
];
341 tabmsg_len
= strlen(tabmsg
);
343 printf("\1#8\033[%d;%dH%s",
344 TABMSG_ROW
, 1 + HSHIFT
+ ((WIDTH
- tabmsg_len
) >> 1), tabmsg
);
345 printf("\1#0\033[%d;1H", END_ROW
);
348 static void clear_screen(void)
350 fputs("\033e\033%@\033)0\033(B\1#0\033[?25l\033[2J", stdout
);
353 static void display_help(const char *text
)
360 printf("\1#0\033[%d;1H", HELPMSG_ROW
);
362 printf("\2#16\033[%d;1H", HELPMSG_ROW
);
365 for (p
= text
, row
= HELPMSG_ROW
; *p
&& row
<= HELPMSGEND_ROW
; p
++) {
373 printf("\033[K\033[%d;1H", ++row
);
380 fputs("\033[K", stdout
);
382 while (row
<= HELPMSGEND_ROW
) {
383 printf("\033[K\033[%d;1H", ++row
);
387 static void show_fkey(int key
)
437 if (cm
->fkeyhelp
[fkey
].textname
)
438 key
= show_message_file(cm
->fkeyhelp
[fkey
].textname
,
439 cm
->fkeyhelp
[fkey
].background
);
445 static const char *edit_cmdline(const char *input
, int top
)
447 static char cmdline
[MAX_CMDLINE_LEN
];
448 int key
, len
, prev_len
, cursor
;
449 int redraw
= 1; /* We enter with the menu already drawn */
451 strlcpy(cmdline
, input
, MAX_CMDLINE_LEN
);
452 cmdline
[MAX_CMDLINE_LEN
- 1] = '\0';
454 len
= cursor
= strlen(cmdline
);
459 /* Clear and redraw whole screen */
460 /* Enable ASCII on G0 and DEC VT on G1; do it in this order
461 to avoid confusing the Linux console */
463 draw_menu(-1, top
, 1);
468 /* Redraw the command line */
469 printf("\033[?25l\033[%d;1H\1#9> \2#10%s",
470 CMDLINE_ROW
, pad_line(cmdline
, 0, max(len
, prev_len
)));
471 printf("\2#10\033[%d;3H%s\033[?25h",
472 CMDLINE_ROW
, pad_line(cmdline
, 0, cursor
));
495 memmove(cmdline
+ cursor
- 1, cmdline
+ cursor
,
506 memmove(cmdline
+ cursor
, cmdline
+ cursor
+ 1, len
- cursor
);
522 int prevcursor
= cursor
;
524 while (cursor
&& my_isspace(cmdline
[cursor
- 1]))
527 while (cursor
&& !my_isspace(cmdline
[cursor
- 1]))
530 memmove(cmdline
+ cursor
, cmdline
+ prevcursor
,
531 len
- prevcursor
+ 1);
532 len
-= (prevcursor
- cursor
);
548 putchar(cmdline
[cursor
++]);
554 cmdline
[len
= cursor
] = '\0';
592 if (key
>= ' ' && key
<= 0xFF && len
< MAX_CMDLINE_LEN
- 1) {
595 cmdline
[++len
] = '\0';
600 memmove(cmdline
+ cursor
+ 1, cmdline
+ cursor
,
602 cmdline
[cursor
++] = key
;
612 static inline int shift_is_held(void)
614 uint8_t shift_bits
= *(uint8_t *) 0x417;
616 return !!(shift_bits
& 0x5d); /* Caps/Scroll/Alt/Shift */
619 static void print_timeout_message(int tol
, int row
, const char *msg
)
621 static int last_msg_len
= 0;
623 int nc
= 0, nnc
, padc
;
624 const char *tp
= msg
;
628 while ((size_t) (tq
- buf
) < (sizeof buf
- 16) && (tc
= *tp
)) {
631 nnc
= sprintf(tq
, "\2#15%d\2#14", tol
);
633 nc
+= nnc
- 8; /* 8 formatting characters */
634 } else if (tc
== '{') {
635 /* Deal with {singular[,dual],plural} constructs */
642 memset(tx
, 0, sizeof tx
);
646 while (*tp
&& *tp
!= '}') {
647 if (*tp
== ',' && n
< 2) {
657 tp
++; /* Skip final bracket */
664 /* Now [0] is singular, [1] is dual, and [2] is plural,
665 even if the user only specified some of them. */
679 for (tpp
= tx
[n
].s
; tpp
< tx
[n
].e
; tpp
++) {
680 if ((size_t) (tq
- buf
) < (sizeof buf
)) {
692 if (nc
>= last_msg_len
) {
695 padc
= (last_msg_len
- nc
+ 1) >> 1;
698 printf("\033[%d;%dH\2#14%*s%s%*s", row
,
699 HSHIFT
+ 1 + ((WIDTH
- nc
) >> 1) - padc
,
700 padc
, "", buf
, padc
, "");
705 /* Set the background screen, etc. */
706 static void prepare_screen_for_menu(void)
708 console_color_table
= cm
->color_table
;
709 console_color_table_size
= menu_color_table_size
;
710 set_background(cm
->menu_background
);
713 static const char *do_hidden_menu(void)
716 int timeout_left
, this_timeout
;
720 if (!setjmp(timeout_jump
)) {
721 timeout_left
= cm
->timeout
;
723 while (!cm
->timeout
|| timeout_left
) {
724 int tol
= timeout_left
/ CLK_TCK
;
726 print_timeout_message(tol
, HIDDEN_ROW
, cm
->messages
[MSG_AUTOBOOT
]);
728 this_timeout
= min(timeout_left
, CLK_TCK
);
729 key
= mygetkey(this_timeout
);
731 if (key
!= KEY_NONE
) {
732 /* Clear the message from the screen */
733 print_timeout_message(0, HIDDEN_ROW
, "");
734 return hide_key
[key
]; /* NULL if no MENU HIDEKEY in effect */
737 timeout_left
-= this_timeout
;
741 /* Clear the message from the screen */
742 print_timeout_message(0, HIDDEN_ROW
, "");
745 return cm
->ontimeout
;
747 return cm
->menu_entries
[cm
->defentry
]->cmdline
; /* Default entry */
750 static const char *run_menu(void)
754 volatile int entry
= cm
->curentry
;
756 volatile int top
= cm
->curtop
;
758 int clear
= 1, to_clear
;
759 const char *cmdline
= NULL
;
760 volatile clock_t key_timeout
, timeout_left
, this_timeout
;
761 const struct menu_entry
*me
;
764 /* Note: for both key_timeout and timeout == 0 means no limit */
765 timeout_left
= key_timeout
= cm
->timeout
;
767 /* If we're in shiftkey mode, exit immediately unless a shift key
769 if (shiftkey
&& !shift_is_held()) {
770 return cm
->menu_entries
[cm
->defentry
]->cmdline
;
775 /* Do this before hiddenmenu handling, so we show the background */
776 prepare_screen_for_menu();
778 /* Handle hiddenmenu */
780 cmdline
= do_hidden_menu();
784 /* Otherwise display the menu now; the timeout has already been
785 cancelled, since the user pressed a key. */
790 /* Handle both local and global timeout */
791 if (setjmp(timeout_jump
)) {
792 entry
= cm
->defentry
;
794 if (top
< 0 || top
< entry
- MENU_ROWS
+ 1)
795 top
= max(0, entry
- MENU_ROWS
+ 1);
796 else if (top
> entry
|| top
> max(0, cm
->nentries
- MENU_ROWS
))
797 top
= min(entry
, max(0, cm
->nentries
- MENU_ROWS
));
799 draw_menu(cm
->ontimeout
? -1 : entry
, top
, 1);
801 cm
->ontimeout
? cm
->ontimeout
: cm
->menu_entries
[entry
]->cmdline
;
808 while (entry
< cm
->nentries
&& is_disabled(cm
->menu_entries
[entry
]))
811 if (entry
>= cm
->nentries
- 1) {
812 entry
= cm
->nentries
- 1;
813 while (entry
> 0 && is_disabled(cm
->menu_entries
[entry
]))
817 me
= cm
->menu_entries
[entry
];
819 if (top
< 0 || top
< entry
- MENU_ROWS
+ 1)
820 top
= max(0, entry
- MENU_ROWS
+ 1);
821 else if (top
> entry
|| top
> max(0, cm
->nentries
- MENU_ROWS
))
822 top
= min(entry
, max(0, cm
->nentries
- MENU_ROWS
));
824 /* Start with a clear screen */
826 /* Clear and redraw whole screen */
827 /* Enable ASCII on G0 and DEC VT on G1; do it in this order
828 to avoid confusing the Linux console */
830 prepare_screen_for_menu();
833 prev_entry
= prev_top
= -1;
836 if (top
!= prev_top
) {
837 draw_menu(entry
, top
, 1);
838 display_help(me
->helptext
);
839 } else if (entry
!= prev_entry
) {
840 draw_row(prev_entry
- top
+ 4 + VSHIFT
, entry
, top
, 0, 0);
841 draw_row(entry
- top
+ 4 + VSHIFT
, entry
, top
, 0, 0);
842 display_help(me
->helptext
);
847 cm
->curentry
= entry
;
850 /* Cursor movement cancels timeout */
851 if (entry
!= cm
->defentry
)
855 int tol
= timeout_left
/ CLK_TCK
;
856 print_timeout_message(tol
, TIMEOUT_ROW
, cm
->messages
[MSG_AUTOBOOT
]);
862 if (hotkey
&& me
->immediate
) {
863 /* If the hotkey was flagged immediate, simulate pressing ENTER */
866 this_timeout
= min(min(key_timeout
, timeout_left
),
868 key
= mygetkey(this_timeout
);
870 if (key
!= KEY_NONE
) {
871 timeout_left
= key_timeout
;
873 printf("\033[%d;1H\1#0\033[K", TIMEOUT_ROW
);
880 case KEY_NONE
: /* Timeout */
881 /* This is somewhat hacky, but this at least lets the user
882 know what's going on, and still deals with "phantom inputs"
883 e.g. on serial ports.
885 Warning: a timeout will boot the default entry without any
888 if (timeout_left
<= this_timeout
)
889 longjmp(timeout_jump
, 1);
891 timeout_left
-= this_timeout
;
901 key_timeout
= 0; /* Cancels timeout */
904 done
= ask_passwd(me
->passwd
);
910 switch (me
->action
) {
912 cmdline
= me
->cmdline
;
920 entry
= cm
->curentry
;
924 /* Quit menu system */
927 draw_row(entry
- top
+ 4 + VSHIFT
, -1, top
, 0, 0);
930 key
= show_message_file(me
->cmdline
, me
->background
);
931 /* If the exit was an F-key, display that help screen */
941 if (done
&& !me
->passwd
) {
942 /* Only save a new default if we don't have a password... */
943 if (me
->save
&& me
->label
) {
944 syslinux_setadv(ADV_MENUSAVE
, strlen(me
->label
), me
->label
);
945 syslinux_adv_write();
956 if (!is_disabled(cm
->menu_entries
[entry
]))
963 while (entry
< cm
->nentries
- 1) {
965 if (entry
>= top
+ MENU_ROWS
)
967 if (!is_disabled(cm
->menu_entries
[entry
]))
978 while (entry
> 0 && is_disabled(cm
->menu_entries
[entry
])) {
992 while (entry
< cm
->nentries
- 1
993 && is_disabled(cm
->menu_entries
[entry
])) {
995 if (entry
>= top
+ MENU_ROWS
)
1004 if (!is_disabled(cm
->menu_entries
[entry
]))
1010 while (entry
< cm
->nentries
- 1) {
1013 if (!is_disabled(cm
->menu_entries
[entry
]))
1025 entry
= cm
->nentries
- 1;
1026 top
= max(0, cm
->nentries
- MENU_ROWS
);
1046 if (cm
->allowedit
&& me
->action
== MA_CMD
) {
1049 key_timeout
= 0; /* Cancels timeout */
1050 draw_row(entry
- top
+ 4 + VSHIFT
, -1, top
, 0, 0);
1052 if (cm
->menu_master_passwd
) {
1053 ok
= ask_passwd(NULL
);
1055 draw_menu(-1, top
, 0);
1057 /* Erase [Tab] message and help text */
1058 printf("\033[%d;1H\1#0\033[K", TABMSG_ROW
);
1063 cmdline
= edit_cmdline(me
->cmdline
, top
);
1065 clear
= 1; /* In case we hit [Esc] and done is null */
1067 draw_row(entry
- top
+ 4 + VSHIFT
, entry
, top
, 0, 0);
1071 case KEY_CTRL('C'): /* Ctrl-C */
1072 case KEY_ESC
: /* Esc */
1076 entry
= cm
->curentry
;
1078 } else if (cm
->allowedit
) {
1083 draw_row(entry
- top
+ 4 + VSHIFT
, -1, top
, 0, 0);
1085 if (cm
->menu_master_passwd
)
1086 done
= ask_passwd(NULL
);
1090 if (key
> 0 && key
< 0xFF) {
1091 key
&= ~0x20; /* Upper case */
1092 if (cm
->menu_hotkeys
[key
]) {
1094 entry
= cm
->menu_hotkeys
[key
]->entry
;
1095 /* Should we commit at this point? */
1103 printf("\033[?25h"); /* Show cursor */
1105 /* Return the label name so localboot and ipappend work */
1109 int main(int argc
, char *argv
[])
1111 const char *cmdline
;
1118 parse_configs(argv
+ 1);
1121 * We don't start the console until we have parsed the configuration
1122 * file, since the configuration file might impact the console
1123 * configuration, e.g. MENU RESOLUTION.
1126 if (getscreensize(1, &rows
, &cols
)) {
1127 /* Unknown screen size? */
1132 /* Some postprocessing for all menus */
1133 for (m
= menu_list
; m
; m
= m
->next
) {
1134 if (!m
->mparm
[P_WIDTH
])
1135 m
->mparm
[P_WIDTH
] = cols
;
1137 /* If anyone has specified negative parameters, consider them
1138 relative to the bottom row of the screen. */
1139 for (i
= 0; i
< NPARAMS
; i
++)
1140 if (m
->mparm
[i
] < 0)
1141 m
->mparm
[i
] = max(m
->mparm
[i
] + rows
, 0);
1146 if (!cm
->nentries
) {
1147 fputs("Initial menu has no LABEL entries!\n", stdout
);
1148 return 1; /* Error! */
1152 local_cursor_enable(true);
1153 cmdline
= run_menu();
1158 local_cursor_enable(false);
1159 printf("\033[?25h\033[%d;1H\033[0m", END_ROW
);
1162 uint32_t type
= parse_image_type(cmdline
);
1164 execute(cmdline
, type
, false);
1166 type
= parse_image_type(cm
->onerror
);
1167 execute(cm
->onerror
, type
, true);
1170 return 0; /* Exit */