1 /* menu_text.c - Basic text menu implementation. */
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2003,2004,2005,2006,2007,2008,2009 Free Software Foundation, Inc.
6 * GRUB 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, either version 3 of the License, or
9 * (at your option) any later version.
11 * GRUB is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
20 #include <grub/normal.h>
21 #include <grub/term.h>
22 #include <grub/misc.h>
23 #include <grub/loader.h>
25 #include <grub/time.h>
27 #include <grub/menu_viewer.h>
28 #include <grub/i18n.h>
30 /* Time to delay after displaying an error message about a default/fallback
31 entry failing to boot. */
32 #define DEFAULT_ENTRY_ERROR_DELAY_MS 2500
34 static grub_uint8_t grub_color_menu_normal
;
35 static grub_uint8_t grub_color_menu_highlight
;
37 /* Wait until the user pushes any key so that the user
38 can see what happened. */
40 grub_wait_after_message (void)
42 grub_printf ("\nPress any key to continue...");
43 (void) grub_getkey ();
52 grub_setcolorstate (GRUB_TERM_COLOR_NORMAL
);
54 grub_gotoxy (GRUB_TERM_MARGIN
, GRUB_TERM_TOP_BORDER_Y
);
55 grub_putcode (GRUB_TERM_DISP_UL
);
56 for (i
= 0; i
< (unsigned) GRUB_TERM_BORDER_WIDTH
- 2; i
++)
57 grub_putcode (GRUB_TERM_DISP_HLINE
);
58 grub_putcode (GRUB_TERM_DISP_UR
);
60 for (i
= 0; i
< (unsigned) GRUB_TERM_NUM_ENTRIES
; i
++)
62 grub_gotoxy (GRUB_TERM_MARGIN
, GRUB_TERM_TOP_BORDER_Y
+ i
+ 1);
63 grub_putcode (GRUB_TERM_DISP_VLINE
);
64 grub_gotoxy (GRUB_TERM_MARGIN
+ GRUB_TERM_BORDER_WIDTH
- 1,
65 GRUB_TERM_TOP_BORDER_Y
+ i
+ 1);
66 grub_putcode (GRUB_TERM_DISP_VLINE
);
69 grub_gotoxy (GRUB_TERM_MARGIN
,
70 GRUB_TERM_TOP_BORDER_Y
+ GRUB_TERM_NUM_ENTRIES
+ 1);
71 grub_putcode (GRUB_TERM_DISP_LL
);
72 for (i
= 0; i
< (unsigned) GRUB_TERM_BORDER_WIDTH
- 2; i
++)
73 grub_putcode (GRUB_TERM_DISP_HLINE
);
74 grub_putcode (GRUB_TERM_DISP_LR
);
76 grub_setcolorstate (GRUB_TERM_COLOR_NORMAL
);
78 grub_gotoxy (GRUB_TERM_MARGIN
,
79 (GRUB_TERM_TOP_BORDER_Y
+ GRUB_TERM_NUM_ENTRIES
80 + GRUB_TERM_MARGIN
+ 1));
84 print_message (int nested
, int edit
)
86 grub_setcolorstate (GRUB_TERM_COLOR_NORMAL
);
91 Minimum Emacs-like screen editing is supported. TAB lists\n\
92 completions. Press Ctrl-x to boot, Ctrl-c for a command-line\n\
93 or ESC to return menu.");
98 Use the %C and %C keys to select which entry is highlighted.\n"),
99 (grub_uint32_t
) GRUB_TERM_DISP_UP
, (grub_uint32_t
) GRUB_TERM_DISP_DOWN
);
101 Press enter to boot the selected OS, \'e\' to edit the\n\
102 commands before booting or \'c\' for a command-line.");
105 ESC to return previous menu.");
110 print_entry (int y
, int highlight
, grub_menu_entry_t entry
)
114 grub_size_t title_len
;
116 grub_uint32_t
*unicode_title
;
118 grub_uint8_t old_color_normal
, old_color_highlight
;
120 title
= entry
? entry
->title
: "";
121 title_len
= grub_strlen (title
);
122 unicode_title
= grub_malloc (title_len
* sizeof (*unicode_title
));
124 /* XXX How to show this error? */
127 len
= grub_utf8_to_ucs4 (unicode_title
, title_len
,
128 (grub_uint8_t
*) title
, -1, 0);
131 /* It is an invalid sequence. */
132 grub_free (unicode_title
);
136 grub_getcolor (&old_color_normal
, &old_color_highlight
);
137 grub_setcolor (grub_color_menu_normal
, grub_color_menu_highlight
);
138 grub_setcolorstate (highlight
139 ? GRUB_TERM_COLOR_HIGHLIGHT
140 : GRUB_TERM_COLOR_NORMAL
);
142 grub_gotoxy (GRUB_TERM_LEFT_BORDER_X
+ GRUB_TERM_MARGIN
, y
);
144 for (x
= GRUB_TERM_LEFT_BORDER_X
+ GRUB_TERM_MARGIN
+ 1, i
= 0;
145 x
< GRUB_TERM_LEFT_BORDER_X
+ GRUB_TERM_BORDER_WIDTH
- GRUB_TERM_MARGIN
;
149 && x
<= (GRUB_TERM_LEFT_BORDER_X
+ GRUB_TERM_BORDER_WIDTH
150 - GRUB_TERM_MARGIN
- 1))
154 width
= grub_getcharwidth (unicode_title
[i
]);
156 if (x
+ width
> (GRUB_TERM_LEFT_BORDER_X
+ GRUB_TERM_BORDER_WIDTH
157 - GRUB_TERM_MARGIN
- 1))
158 grub_putcode (GRUB_TERM_DISP_RIGHT
);
160 grub_putcode (unicode_title
[i
]);
170 grub_setcolorstate (GRUB_TERM_COLOR_NORMAL
);
173 grub_gotoxy (GRUB_TERM_CURSOR_X
, y
);
175 grub_setcolor (old_color_normal
, old_color_highlight
);
176 grub_setcolorstate (GRUB_TERM_COLOR_NORMAL
);
177 grub_free (unicode_title
);
181 print_entries (grub_menu_t menu
, int first
, int offset
)
186 grub_gotoxy (GRUB_TERM_LEFT_BORDER_X
+ GRUB_TERM_BORDER_WIDTH
,
187 GRUB_TERM_FIRST_ENTRY_Y
);
190 grub_putcode (GRUB_TERM_DISP_UP
);
194 e
= grub_menu_get_entry (menu
, first
);
196 for (i
= 0; i
< GRUB_TERM_NUM_ENTRIES
; i
++)
198 print_entry (GRUB_TERM_FIRST_ENTRY_Y
+ i
, offset
== i
, e
);
203 grub_gotoxy (GRUB_TERM_LEFT_BORDER_X
+ GRUB_TERM_BORDER_WIDTH
,
204 GRUB_TERM_TOP_BORDER_Y
+ GRUB_TERM_NUM_ENTRIES
);
207 grub_putcode (GRUB_TERM_DISP_DOWN
);
211 grub_gotoxy (GRUB_TERM_CURSOR_X
, GRUB_TERM_FIRST_ENTRY_Y
+ offset
);
214 /* Initialize the screen. If NESTED is non-zero, assume that this menu
215 is run from another menu or a command-line. If EDIT is non-zero, show
216 a message for the menu entry editor. */
218 grub_menu_init_page (int nested
, int edit
)
220 grub_uint8_t old_color_normal
, old_color_highlight
;
222 grub_getcolor (&old_color_normal
, &old_color_highlight
);
224 /* By default, use the same colors for the menu. */
225 grub_color_menu_normal
= old_color_normal
;
226 grub_color_menu_highlight
= old_color_highlight
;
228 /* Then give user a chance to replace them. */
229 grub_parse_color_name_pair (&grub_color_menu_normal
, grub_env_get ("menu_color_normal"));
230 grub_parse_color_name_pair (&grub_color_menu_highlight
, grub_env_get ("menu_color_highlight"));
232 grub_normal_init_page ();
233 grub_setcolor (grub_color_menu_normal
, grub_color_menu_highlight
);
235 grub_setcolor (old_color_normal
, old_color_highlight
);
236 print_message (nested
, edit
);
239 /* Get the entry number from the variable NAME. */
241 get_entry_number (const char *name
)
246 val
= grub_env_get (name
);
252 entry
= (int) grub_strtoul (val
, 0, 0);
254 if (grub_errno
!= GRUB_ERR_NONE
)
256 grub_errno
= GRUB_ERR_NONE
;
266 print_timeout (int timeout
, int offset
, int second_stage
)
268 /* NOTE: Do not remove the trailing space characters.
269 They are required to clear the line. */
270 char *msg
= " The highlighted entry will be booted automatically in %ds. ";
271 char *msg_end
= grub_strchr (msg
, '%');
273 grub_gotoxy (second_stage
? (msg_end
- msg
) : 0, GRUB_TERM_HEIGHT
- 3);
274 grub_printf (second_stage
? msg_end
: msg
, timeout
);
275 grub_gotoxy (GRUB_TERM_CURSOR_X
, GRUB_TERM_FIRST_ENTRY_Y
+ offset
);
279 /* Show the menu and handle menu entry selection. Returns the menu entry
280 index that should be executed or -1 if no entry should be executed (e.g.,
281 Esc pressed to exit a sub-menu or switching menu viewers).
282 If the return value is not -1, then *AUTO_BOOT is nonzero iff the menu
283 entry to be executed is a result of an automatic default selection because
286 run_menu (grub_menu_t menu
, int nested
, int *auto_boot
)
289 grub_uint64_t saved_time
;
295 default_entry
= get_entry_number ("default");
297 /* If DEFAULT_ENTRY is not within the menu entries, fall back to
299 if (default_entry
< 0 || default_entry
>= menu
->size
)
302 /* If timeout is 0, drawing is pointless (and ugly). */
303 if (grub_menu_get_timeout () == 0)
306 return default_entry
;
309 offset
= default_entry
;
310 if (offset
> GRUB_TERM_NUM_ENTRIES
- 1)
312 first
= offset
- (GRUB_TERM_NUM_ENTRIES
- 1);
313 offset
= GRUB_TERM_NUM_ENTRIES
- 1;
316 /* Initialize the time. */
317 saved_time
= grub_get_time_ms ();
321 grub_menu_init_page (nested
, 0);
322 print_entries (menu
, first
, offset
);
325 timeout
= grub_menu_get_timeout ();
328 print_timeout (timeout
, offset
, 0);
333 timeout
= grub_menu_get_timeout ();
337 grub_uint64_t current_time
;
339 current_time
= grub_get_time_ms ();
340 if (current_time
- saved_time
>= 1000)
343 grub_menu_set_timeout (timeout
);
344 saved_time
= current_time
;
345 print_timeout (timeout
, offset
, 1);
351 grub_env_unset ("timeout");
353 return default_entry
;
356 if (grub_checkkey () >= 0 || timeout
< 0)
358 c
= GRUB_TERM_ASCII_CHAR (grub_getkey ());
362 grub_gotoxy (0, GRUB_TERM_HEIGHT
- 3);
365 grub_env_unset ("timeout");
366 grub_env_unset ("fallback");
367 grub_gotoxy (GRUB_TERM_CURSOR_X
, GRUB_TERM_FIRST_ENTRY_Y
+ offset
);
375 print_entries (menu
, first
, offset
);
379 offset
= menu
->size
- 1;
380 if (offset
> GRUB_TERM_NUM_ENTRIES
- 1)
382 first
= offset
- (GRUB_TERM_NUM_ENTRIES
- 1);
383 offset
= GRUB_TERM_NUM_ENTRIES
- 1;
385 print_entries (menu
, first
, offset
);
392 print_entry (GRUB_TERM_FIRST_ENTRY_Y
+ offset
, 0,
393 grub_menu_get_entry (menu
, first
+ offset
));
395 print_entry (GRUB_TERM_FIRST_ENTRY_Y
+ offset
, 1,
396 grub_menu_get_entry (menu
, first
+ offset
));
401 print_entries (menu
, first
, offset
);
407 if (menu
->size
> first
+ offset
+ 1)
409 if (offset
< GRUB_TERM_NUM_ENTRIES
- 1)
411 print_entry (GRUB_TERM_FIRST_ENTRY_Y
+ offset
, 0,
412 grub_menu_get_entry (menu
, first
+ offset
));
414 print_entry (GRUB_TERM_FIRST_ENTRY_Y
+ offset
, 1,
415 grub_menu_get_entry (menu
, first
+ offset
));
420 print_entries (menu
, first
, offset
);
425 case GRUB_TERM_PPAGE
:
432 first
-= GRUB_TERM_NUM_ENTRIES
;
440 print_entries (menu
, first
, offset
);
443 case GRUB_TERM_NPAGE
:
446 offset
+= GRUB_TERM_NUM_ENTRIES
- 1;
447 if (first
+ offset
>= menu
->size
)
449 offset
= menu
->size
- first
- 1;
454 first
+= GRUB_TERM_NUM_ENTRIES
;
456 if (first
+ offset
>= menu
->size
)
458 first
-= GRUB_TERM_NUM_ENTRIES
;
459 offset
+= GRUB_TERM_NUM_ENTRIES
;
461 if (offset
> menu
->size
- 1 ||
462 offset
> GRUB_TERM_NUM_ENTRIES
- 1)
464 offset
= menu
->size
- first
- 1;
466 if (offset
> GRUB_TERM_NUM_ENTRIES
)
468 first
+= offset
- GRUB_TERM_NUM_ENTRIES
+ 1;
469 offset
= GRUB_TERM_NUM_ENTRIES
- 1;
473 print_entries (menu
, first
, offset
);
481 return first
+ offset
;
492 grub_cmdline_run (1);
497 grub_menu_entry_t e
= grub_menu_get_entry (menu
, first
+ offset
);
499 grub_menu_entry_run (e
);
511 /* Never reach here. */
515 /* Callback invoked immediately before a menu entry is executed. */
517 notify_booting (grub_menu_entry_t entry
,
518 void *userdata
__attribute__((unused
)))
520 grub_printf (" Booting \'%s\'\n\n", entry
->title
);
523 /* Callback invoked when a default menu entry executed because of a timeout
524 has failed and an attempt will be made to execute the next fallback
527 notify_fallback (grub_menu_entry_t entry
,
528 void *userdata
__attribute__((unused
)))
530 grub_printf ("\n Falling back to \'%s\'\n\n", entry
->title
);
531 grub_millisleep (DEFAULT_ENTRY_ERROR_DELAY_MS
);
534 /* Callback invoked when a menu entry has failed and there is no remaining
535 fallback entry to attempt. */
537 notify_execution_failure (void *userdata
__attribute__((unused
)))
539 if (grub_errno
!= GRUB_ERR_NONE
)
542 grub_errno
= GRUB_ERR_NONE
;
544 grub_printf ("\n Failed to boot default entries.\n");
545 grub_wait_after_message ();
548 /* Callbacks used by the text menu to provide user feedback when menu entries
550 static struct grub_menu_execute_callback execution_callback
=
552 .notify_booting
= notify_booting
,
553 .notify_fallback
= notify_fallback
,
554 .notify_failure
= notify_execution_failure
558 show_text_menu (grub_menu_t menu
, int nested
)
566 boot_entry
= run_menu (menu
, nested
, &auto_boot
);
570 e
= grub_menu_get_entry (menu
, boot_entry
);
572 continue; /* Menu is empty. */
579 grub_menu_execute_with_fallback (menu
, e
, &execution_callback
, 0);
583 grub_errno
= GRUB_ERR_NONE
;
584 grub_menu_execute_entry (e
);
585 if (grub_errno
!= GRUB_ERR_NONE
)
588 grub_errno
= GRUB_ERR_NONE
;
589 grub_wait_after_message ();
594 return GRUB_ERR_NONE
;
597 struct grub_menu_viewer grub_normal_text_menu_viewer
=
600 .show_menu
= show_text_menu