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>
29
/* Time to delay after displaying an error message about a default/fallback
30
entry failing to boot. */
31
#define DEFAULT_ENTRY_ERROR_DELAY_MS 2500
33
static grub_uint8_t grub_color_menu_normal;
34
static grub_uint8_t grub_color_menu_highlight;
36
/* Wait until the user pushes any key so that the user
37
can see what happened. */
39
grub_wait_after_message (void)
41
grub_printf ("\nPress any key to continue...");
42
(void) grub_getkey ();
51
grub_setcolorstate (GRUB_TERM_COLOR_NORMAL);
53
grub_gotoxy (GRUB_TERM_MARGIN, GRUB_TERM_TOP_BORDER_Y);
54
grub_putcode (GRUB_TERM_DISP_UL);
55
for (i = 0; i < (unsigned) GRUB_TERM_BORDER_WIDTH - 2; i++)
56
grub_putcode (GRUB_TERM_DISP_HLINE);
57
grub_putcode (GRUB_TERM_DISP_UR);
59
for (i = 0; i < (unsigned) GRUB_TERM_NUM_ENTRIES; i++)
61
grub_gotoxy (GRUB_TERM_MARGIN, GRUB_TERM_TOP_BORDER_Y + i + 1);
62
grub_putcode (GRUB_TERM_DISP_VLINE);
63
grub_gotoxy (GRUB_TERM_MARGIN + GRUB_TERM_BORDER_WIDTH - 1,
64
GRUB_TERM_TOP_BORDER_Y + i + 1);
65
grub_putcode (GRUB_TERM_DISP_VLINE);
68
grub_gotoxy (GRUB_TERM_MARGIN,
69
GRUB_TERM_TOP_BORDER_Y + GRUB_TERM_NUM_ENTRIES + 1);
70
grub_putcode (GRUB_TERM_DISP_LL);
71
for (i = 0; i < (unsigned) GRUB_TERM_BORDER_WIDTH - 2; i++)
72
grub_putcode (GRUB_TERM_DISP_HLINE);
73
grub_putcode (GRUB_TERM_DISP_LR);
75
grub_setcolorstate (GRUB_TERM_COLOR_NORMAL);
77
grub_gotoxy (GRUB_TERM_MARGIN,
78
(GRUB_TERM_TOP_BORDER_Y + GRUB_TERM_NUM_ENTRIES
79
+ GRUB_TERM_MARGIN + 1));
83
print_message (int nested, int edit)
85
grub_setcolorstate (GRUB_TERM_COLOR_NORMAL);
90
Minimum Emacs-like screen editing is supported. TAB lists\n\
91
completions. Press Ctrl-x to boot, Ctrl-c for a command-line\n\
92
or ESC to return menu.");
97
Use the %C and %C keys to select which entry is highlighted.\n",
98
(grub_uint32_t) GRUB_TERM_DISP_UP, (grub_uint32_t) GRUB_TERM_DISP_DOWN);
100
Press enter to boot the selected OS, \'e\' to edit the\n\
101
commands before booting or \'c\' for a command-line.");
104
ESC to return previous menu.");
109
print_entry (int y, int highlight, grub_menu_entry_t entry)
113
grub_size_t title_len;
115
grub_uint32_t *unicode_title;
117
grub_uint8_t old_color_normal, old_color_highlight;
119
title = entry ? entry->title : "";
120
title_len = grub_strlen (title);
121
unicode_title = grub_malloc (title_len * sizeof (*unicode_title));
123
/* XXX How to show this error? */
126
len = grub_utf8_to_ucs4 (unicode_title, title_len,
127
(grub_uint8_t *) title, -1, 0);
130
/* It is an invalid sequence. */
131
grub_free (unicode_title);
135
grub_getcolor (&old_color_normal, &old_color_highlight);
136
grub_setcolor (grub_color_menu_normal, grub_color_menu_highlight);
137
grub_setcolorstate (highlight
138
? GRUB_TERM_COLOR_HIGHLIGHT
139
: GRUB_TERM_COLOR_NORMAL);
141
grub_gotoxy (GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_MARGIN, y);
143
for (x = GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_MARGIN + 1, i = 0;
144
x < GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_BORDER_WIDTH - GRUB_TERM_MARGIN;
148
&& x <= (GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_BORDER_WIDTH
149
- GRUB_TERM_MARGIN - 1))
153
width = grub_getcharwidth (unicode_title[i]);
155
if (x + width > (GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_BORDER_WIDTH
156
- GRUB_TERM_MARGIN - 1))
157
grub_putcode (GRUB_TERM_DISP_RIGHT);
159
grub_putcode (unicode_title[i]);
169
grub_setcolorstate (GRUB_TERM_COLOR_NORMAL);
172
grub_gotoxy (GRUB_TERM_CURSOR_X, y);
174
grub_setcolor (old_color_normal, old_color_highlight);
175
grub_setcolorstate (GRUB_TERM_COLOR_NORMAL);
176
grub_free (unicode_title);
180
print_entries (grub_menu_t menu, int first, int offset)
185
grub_gotoxy (GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_BORDER_WIDTH,
186
GRUB_TERM_FIRST_ENTRY_Y);
189
grub_putcode (GRUB_TERM_DISP_UP);
193
e = grub_menu_get_entry (menu, first);
195
for (i = 0; i < GRUB_TERM_NUM_ENTRIES; i++)
197
print_entry (GRUB_TERM_FIRST_ENTRY_Y + i, offset == i, e);
202
grub_gotoxy (GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_BORDER_WIDTH,
203
GRUB_TERM_TOP_BORDER_Y + GRUB_TERM_NUM_ENTRIES);
206
grub_putcode (GRUB_TERM_DISP_DOWN);
210
grub_gotoxy (GRUB_TERM_CURSOR_X, GRUB_TERM_FIRST_ENTRY_Y + offset);
213
/* Initialize the screen. If NESTED is non-zero, assume that this menu
214
is run from another menu or a command-line. If EDIT is non-zero, show
215
a message for the menu entry editor. */
217
grub_menu_init_page (int nested, int edit)
219
grub_uint8_t old_color_normal, old_color_highlight;
221
grub_getcolor (&old_color_normal, &old_color_highlight);
223
/* By default, use the same colors for the menu. */
224
grub_color_menu_normal = old_color_normal;
225
grub_color_menu_highlight = old_color_highlight;
227
/* Then give user a chance to replace them. */
228
grub_parse_color_name_pair (&grub_color_menu_normal, grub_env_get ("menu_color_normal"));
229
grub_parse_color_name_pair (&grub_color_menu_highlight, grub_env_get ("menu_color_highlight"));
231
grub_normal_init_page ();
232
grub_setcolor (grub_color_menu_normal, grub_color_menu_highlight);
234
grub_setcolor (old_color_normal, old_color_highlight);
235
print_message (nested, edit);
238
/* Get the entry number from the variable NAME. */
240
get_entry_number (const char *name)
245
val = grub_env_get (name);
251
entry = (int) grub_strtoul (val, 0, 0);
253
if (grub_errno != GRUB_ERR_NONE)
255
grub_errno = GRUB_ERR_NONE;
265
print_timeout (int timeout, int offset, int second_stage)
267
/* NOTE: Do not remove the trailing space characters.
268
They are required to clear the line. */
269
char *msg = " The highlighted entry will be booted automatically in %ds. ";
270
char *msg_end = grub_strchr (msg, '%');
272
grub_gotoxy (second_stage ? (msg_end - msg) : 0, GRUB_TERM_HEIGHT - 3);
273
grub_printf (second_stage ? msg_end : msg, timeout);
274
grub_gotoxy (GRUB_TERM_CURSOR_X, GRUB_TERM_FIRST_ENTRY_Y + offset);
278
/* Show the menu and handle menu entry selection. Returns the menu entry
279
index that should be executed or -1 if no entry should be executed (e.g.,
280
Esc pressed to exit a sub-menu or switching menu viewers).
281
If the return value is not -1, then *AUTO_BOOT is nonzero iff the menu
282
entry to be executed is a result of an automatic default selection because
285
run_menu (grub_menu_t menu, int nested, int *auto_boot)
288
grub_uint64_t saved_time;
294
default_entry = get_entry_number ("default");
296
/* If DEFAULT_ENTRY is not within the menu entries, fall back to
298
if (default_entry < 0 || default_entry >= menu->size)
301
/* If timeout is 0, drawing is pointless (and ugly). */
302
if (grub_menu_get_timeout () == 0)
305
return default_entry;
308
offset = default_entry;
309
if (offset > GRUB_TERM_NUM_ENTRIES - 1)
311
first = offset - (GRUB_TERM_NUM_ENTRIES - 1);
312
offset = GRUB_TERM_NUM_ENTRIES - 1;
315
/* Initialize the time. */
316
saved_time = grub_get_time_ms ();
320
grub_menu_init_page (nested, 0);
321
print_entries (menu, first, offset);
324
timeout = grub_menu_get_timeout ();
327
print_timeout (timeout, offset, 0);
332
timeout = grub_menu_get_timeout ();
336
grub_uint64_t current_time;
338
current_time = grub_get_time_ms ();
339
if (current_time - saved_time >= 1000)
342
grub_menu_set_timeout (timeout);
343
saved_time = current_time;
344
print_timeout (timeout, offset, 1);
350
grub_env_unset ("timeout");
352
return default_entry;
355
if (grub_checkkey () >= 0 || timeout < 0)
357
c = GRUB_TERM_ASCII_CHAR (grub_getkey ());
361
grub_gotoxy (0, GRUB_TERM_HEIGHT - 3);
364
grub_env_unset ("timeout");
365
grub_env_unset ("fallback");
366
grub_gotoxy (GRUB_TERM_CURSOR_X, GRUB_TERM_FIRST_ENTRY_Y + offset);
374
print_entries (menu, first, offset);
378
offset = menu->size - 1;
379
if (offset > GRUB_TERM_NUM_ENTRIES - 1)
381
first = offset - (GRUB_TERM_NUM_ENTRIES - 1);
382
offset = GRUB_TERM_NUM_ENTRIES - 1;
384
print_entries (menu, first, offset);
391
print_entry (GRUB_TERM_FIRST_ENTRY_Y + offset, 0,
392
grub_menu_get_entry (menu, first + offset));
394
print_entry (GRUB_TERM_FIRST_ENTRY_Y + offset, 1,
395
grub_menu_get_entry (menu, first + offset));
400
print_entries (menu, first, offset);
406
if (menu->size > first + offset + 1)
408
if (offset < GRUB_TERM_NUM_ENTRIES - 1)
410
print_entry (GRUB_TERM_FIRST_ENTRY_Y + offset, 0,
411
grub_menu_get_entry (menu, first + offset));
413
print_entry (GRUB_TERM_FIRST_ENTRY_Y + offset, 1,
414
grub_menu_get_entry (menu, first + offset));
419
print_entries (menu, first, offset);
424
case GRUB_TERM_PPAGE:
431
first -= GRUB_TERM_NUM_ENTRIES;
439
print_entries (menu, first, offset);
442
case GRUB_TERM_NPAGE:
445
offset += GRUB_TERM_NUM_ENTRIES - 1;
446
if (first + offset >= menu->size)
448
offset = menu->size - first - 1;
453
first += GRUB_TERM_NUM_ENTRIES;
455
if (first + offset >= menu->size)
457
first -= GRUB_TERM_NUM_ENTRIES;
458
offset += GRUB_TERM_NUM_ENTRIES;
460
if (offset > menu->size - 1 ||
461
offset > GRUB_TERM_NUM_ENTRIES - 1)
463
offset = menu->size - first - 1;
465
if (offset > GRUB_TERM_NUM_ENTRIES)
467
first += offset - GRUB_TERM_NUM_ENTRIES + 1;
468
offset = GRUB_TERM_NUM_ENTRIES - 1;
472
print_entries (menu, first, offset);
480
return first + offset;
491
grub_cmdline_run (1);
496
grub_menu_entry_t e = grub_menu_get_entry (menu, first + offset);
498
grub_menu_entry_run (e);
510
/* Never reach here. */
514
/* Callback invoked immediately before a menu entry is executed. */
516
notify_booting (grub_menu_entry_t entry,
517
void *userdata __attribute__((unused)))
519
grub_printf (" Booting \'%s\'\n\n", entry->title);
522
/* Callback invoked when a default menu entry executed because of a timeout
523
has failed and an attempt will be made to execute the next fallback
526
notify_fallback (grub_menu_entry_t entry,
527
void *userdata __attribute__((unused)))
529
grub_printf ("\n Falling back to \'%s\'\n\n", entry->title);
530
grub_millisleep (DEFAULT_ENTRY_ERROR_DELAY_MS);
533
/* Callback invoked when a menu entry has failed and there is no remaining
534
fallback entry to attempt. */
536
notify_execution_failure (void *userdata __attribute__((unused)))
538
if (grub_errno != GRUB_ERR_NONE)
541
grub_errno = GRUB_ERR_NONE;
543
grub_printf ("\n Failed to boot default entries.\n");
544
grub_wait_after_message ();
547
/* Callbacks used by the text menu to provide user feedback when menu entries
549
static struct grub_menu_execute_callback execution_callback =
551
.notify_booting = notify_booting,
552
.notify_fallback = notify_fallback,
553
.notify_failure = notify_execution_failure
557
show_text_menu (grub_menu_t menu, int nested)
565
boot_entry = run_menu (menu, nested, &auto_boot);
569
e = grub_menu_get_entry (menu, boot_entry);
571
continue; /* Menu is empty. */
578
grub_menu_execute_with_fallback (menu, e, &execution_callback, 0);
582
grub_errno = GRUB_ERR_NONE;
583
grub_menu_execute_entry (e);
584
if (grub_errno != GRUB_ERR_NONE)
587
grub_errno = GRUB_ERR_NONE;
588
grub_wait_after_message ();
593
return GRUB_ERR_NONE;
596
struct grub_menu_viewer grub_normal_text_menu_viewer =
599
.show_menu = show_text_menu