1 /* ----------------------------------------------------------------------- *
3 * Copyright 2004-2008 H. Peter Anvin - All Rights Reserved
4 * Copyright 2009-2010 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 strncpy(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
);
730 return NULL
; /* Key pressed */
732 timeout_left
-= this_timeout
;
736 /* Clear the message from the screen */
737 print_timeout_message(0, HIDDEN_ROW
, "");
740 return cm
->ontimeout
;
742 return cm
->menu_entries
[cm
->defentry
]->cmdline
; /* Default entry */
745 static const char *run_menu(void)
749 volatile int entry
= cm
->curentry
;
751 volatile int top
= cm
->curtop
;
753 int clear
= 1, to_clear
;
754 const char *cmdline
= NULL
;
755 volatile clock_t key_timeout
, timeout_left
, this_timeout
;
756 const struct menu_entry
*me
;
759 /* Note: for both key_timeout and timeout == 0 means no limit */
760 timeout_left
= key_timeout
= cm
->timeout
;
762 /* If we're in shiftkey mode, exit immediately unless a shift key
764 if (shiftkey
&& !shift_is_held()) {
765 return cm
->menu_entries
[cm
->defentry
]->cmdline
;
770 /* Do this before hiddenmenu handling, so we show the background */
771 prepare_screen_for_menu();
773 /* Handle hiddenmenu */
775 cmdline
= do_hidden_menu();
779 /* Otherwise display the menu now; the timeout has already been
780 cancelled, since the user pressed a key. */
785 /* Handle both local and global timeout */
786 if (setjmp(timeout_jump
)) {
787 entry
= cm
->defentry
;
789 if (top
< 0 || top
< entry
- MENU_ROWS
+ 1)
790 top
= max(0, entry
- MENU_ROWS
+ 1);
791 else if (top
> entry
|| top
> max(0, cm
->nentries
- MENU_ROWS
))
792 top
= min(entry
, max(0, cm
->nentries
- MENU_ROWS
));
794 draw_menu(cm
->ontimeout
? -1 : entry
, top
, 1);
796 cm
->ontimeout
? cm
->ontimeout
: cm
->menu_entries
[entry
]->cmdline
;
803 while (entry
< cm
->nentries
&& is_disabled(cm
->menu_entries
[entry
]))
806 if (entry
>= cm
->nentries
) {
807 entry
= cm
->nentries
- 1;
808 while (entry
> 0 && is_disabled(cm
->menu_entries
[entry
]))
812 me
= cm
->menu_entries
[entry
];
814 if (top
< 0 || top
< entry
- MENU_ROWS
+ 1)
815 top
= max(0, entry
- MENU_ROWS
+ 1);
816 else if (top
> entry
|| top
> max(0, cm
->nentries
- MENU_ROWS
))
817 top
= min(entry
, max(0, cm
->nentries
- MENU_ROWS
));
819 /* Start with a clear screen */
821 /* Clear and redraw whole screen */
822 /* Enable ASCII on G0 and DEC VT on G1; do it in this order
823 to avoid confusing the Linux console */
825 prepare_screen_for_menu();
828 prev_entry
= prev_top
= -1;
831 if (top
!= prev_top
) {
832 draw_menu(entry
, top
, 1);
833 display_help(me
->helptext
);
834 } else if (entry
!= prev_entry
) {
835 draw_row(prev_entry
- top
+ 4 + VSHIFT
, entry
, top
, 0, 0);
836 draw_row(entry
- top
+ 4 + VSHIFT
, entry
, top
, 0, 0);
837 display_help(me
->helptext
);
842 cm
->curentry
= entry
;
845 /* Cursor movement cancels timeout */
846 if (entry
!= cm
->defentry
)
850 int tol
= timeout_left
/ CLK_TCK
;
851 print_timeout_message(tol
, TIMEOUT_ROW
, cm
->messages
[MSG_AUTOBOOT
]);
857 if (hotkey
&& me
->immediate
) {
858 /* If the hotkey was flagged immediate, simulate pressing ENTER */
861 this_timeout
= min(min(key_timeout
, timeout_left
),
863 key
= mygetkey(this_timeout
);
865 if (key
!= KEY_NONE
) {
866 timeout_left
= key_timeout
;
868 printf("\033[%d;1H\1#0\033[K", TIMEOUT_ROW
);
875 case KEY_NONE
: /* Timeout */
876 /* This is somewhat hacky, but this at least lets the user
877 know what's going on, and still deals with "phantom inputs"
878 e.g. on serial ports.
880 Warning: a timeout will boot the default entry without any
883 if (timeout_left
<= this_timeout
)
884 longjmp(timeout_jump
, 1);
886 timeout_left
-= this_timeout
;
896 key_timeout
= 0; /* Cancels timeout */
899 done
= ask_passwd(me
->passwd
);
905 switch (me
->action
) {
907 cmdline
= me
->cmdline
;
915 entry
= cm
->curentry
;
919 /* Quit menu system */
922 draw_row(entry
- top
+ 4 + VSHIFT
, -1, top
, 0, 0);
929 if (done
&& !me
->passwd
) {
930 /* Only save a new default if we don't have a password... */
931 if (me
->save
&& me
->label
) {
932 syslinux_setadv(ADV_MENUSAVE
, strlen(me
->label
), me
->label
);
933 syslinux_adv_write();
944 if (!is_disabled(cm
->menu_entries
[entry
]))
951 while (entry
< cm
->nentries
- 1) {
953 if (entry
>= top
+ MENU_ROWS
)
955 if (!is_disabled(cm
->menu_entries
[entry
]))
966 while (entry
> 0 && is_disabled(cm
->menu_entries
[entry
])) {
980 while (entry
< cm
->nentries
- 1
981 && is_disabled(cm
->menu_entries
[entry
])) {
983 if (entry
>= top
+ MENU_ROWS
)
992 if (!is_disabled(cm
->menu_entries
[entry
]))
998 while (entry
< cm
->nentries
- 1) {
1001 if (!is_disabled(cm
->menu_entries
[entry
]))
1013 entry
= cm
->nentries
- 1;
1014 top
= max(0, cm
->nentries
- MENU_ROWS
);
1034 if (cm
->allowedit
&& me
->action
== MA_CMD
) {
1037 key_timeout
= 0; /* Cancels timeout */
1038 draw_row(entry
- top
+ 4 + VSHIFT
, -1, top
, 0, 0);
1040 if (cm
->menu_master_passwd
) {
1041 ok
= ask_passwd(NULL
);
1043 draw_menu(-1, top
, 0);
1045 /* Erase [Tab] message and help text */
1046 printf("\033[%d;1H\1#0\033[K", TABMSG_ROW
);
1051 cmdline
= edit_cmdline(me
->cmdline
, top
);
1053 clear
= 1; /* In case we hit [Esc] and done is null */
1055 draw_row(entry
- top
+ 4 + VSHIFT
, entry
, top
, 0, 0);
1059 case KEY_CTRL('C'): /* Ctrl-C */
1060 case KEY_ESC
: /* Esc */
1064 entry
= cm
->curentry
;
1066 } else if (cm
->allowedit
) {
1071 draw_row(entry
- top
+ 4 + VSHIFT
, -1, top
, 0, 0);
1073 if (cm
->menu_master_passwd
)
1074 done
= ask_passwd(NULL
);
1078 if (key
> 0 && key
< 0xFF) {
1079 key
&= ~0x20; /* Upper case */
1080 if (cm
->menu_hotkeys
[key
]) {
1082 entry
= cm
->menu_hotkeys
[key
]->entry
;
1083 /* Should we commit at this point? */
1091 printf("\033[?25h"); /* Show cursor */
1093 /* Return the label name so localboot and ipappend work */
1097 int main(int argc
, char *argv
[])
1099 const char *cmdline
;
1106 parse_configs(argv
+ 1);
1109 * We don't start the console until we have parsed the configuration
1110 * file, since the configuration file might impact the console
1111 * configuration, e.g. MENU RESOLUTION.
1114 if (getscreensize(1, &rows
, &cols
)) {
1115 /* Unknown screen size? */
1120 /* Some postprocessing for all menus */
1121 for (m
= menu_list
; m
; m
= m
->next
) {
1122 if (!m
->mparm
[P_WIDTH
])
1123 m
->mparm
[P_WIDTH
] = cols
;
1125 /* If anyone has specified negative parameters, consider them
1126 relative to the bottom row of the screen. */
1127 for (i
= 0; i
< NPARAMS
; i
++)
1128 if (m
->mparm
[i
] < 0)
1129 m
->mparm
[i
] = max(m
->mparm
[i
] + rows
, 0);
1134 if (!cm
->nentries
) {
1135 fputs("Initial menu has no LABEL entries!\n", stdout
);
1136 return 1; /* Error! */
1140 local_cursor_enable(true);
1141 cmdline
= run_menu();
1146 local_cursor_enable(false);
1147 printf("\033[?25h\033[%d;1H\033[0m", END_ROW
);
1150 execute(cmdline
, KT_NONE
);
1152 execute(cm
->onerror
, KT_NONE
);
1154 return 0; /* Exit */