2009-01-26 Daniel Mierswa <impulze@impulze.org>
[grub2/bean.git] / normal / menu.c
blob7dd5300be5d1e6cc0b7d38262535d48dbd36b300
1 /*
2 * GRUB -- GRand Unified Bootloader
3 * Copyright (C) 2003,2004,2005,2006,2007,2008,2009 Free Software Foundation, Inc.
5 * GRUB is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * GRUB is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
19 #include <grub/normal.h>
20 #include <grub/term.h>
21 #include <grub/misc.h>
22 #include <grub/loader.h>
23 #include <grub/mm.h>
24 #include <grub/time.h>
25 #include <grub/env.h>
26 #include <grub/script.h>
28 static grub_uint8_t grub_color_menu_normal;
29 static grub_uint8_t grub_color_menu_highlight;
31 /* Wait until the user pushes any key so that the user
32 can see what happened. */
33 void
34 grub_wait_after_message (void)
36 grub_printf ("\nPress any key to continue...");
37 (void) grub_getkey ();
40 static void
41 draw_border (void)
43 unsigned i;
45 grub_setcolorstate (GRUB_TERM_COLOR_NORMAL);
47 grub_gotoxy (GRUB_TERM_MARGIN, GRUB_TERM_TOP_BORDER_Y);
48 grub_putcode (GRUB_TERM_DISP_UL);
49 for (i = 0; i < (unsigned) GRUB_TERM_BORDER_WIDTH - 2; i++)
50 grub_putcode (GRUB_TERM_DISP_HLINE);
51 grub_putcode (GRUB_TERM_DISP_UR);
53 for (i = 0; i < (unsigned) GRUB_TERM_NUM_ENTRIES; i++)
55 grub_gotoxy (GRUB_TERM_MARGIN, GRUB_TERM_TOP_BORDER_Y + i + 1);
56 grub_putcode (GRUB_TERM_DISP_VLINE);
57 grub_gotoxy (GRUB_TERM_MARGIN + GRUB_TERM_BORDER_WIDTH - 1,
58 GRUB_TERM_TOP_BORDER_Y + i + 1);
59 grub_putcode (GRUB_TERM_DISP_VLINE);
62 grub_gotoxy (GRUB_TERM_MARGIN,
63 GRUB_TERM_TOP_BORDER_Y + GRUB_TERM_NUM_ENTRIES + 1);
64 grub_putcode (GRUB_TERM_DISP_LL);
65 for (i = 0; i < (unsigned) GRUB_TERM_BORDER_WIDTH - 2; i++)
66 grub_putcode (GRUB_TERM_DISP_HLINE);
67 grub_putcode (GRUB_TERM_DISP_LR);
69 grub_setcolorstate (GRUB_TERM_COLOR_NORMAL);
71 grub_gotoxy (GRUB_TERM_MARGIN,
72 (GRUB_TERM_TOP_BORDER_Y + GRUB_TERM_NUM_ENTRIES
73 + GRUB_TERM_MARGIN + 1));
76 static void
77 print_message (int nested, int edit)
79 grub_setcolorstate (GRUB_TERM_COLOR_NORMAL);
81 if (edit)
83 grub_printf ("\n\
84 Minimum Emacs-like screen editing is supported. TAB lists\n\
85 completions. Press Ctrl-x to boot, Ctrl-c for a command-line\n\
86 or ESC to return menu.");
88 else
90 grub_printf ("\n\
91 Use the %C and %C keys to select which entry is highlighted.\n",
92 (grub_uint32_t) GRUB_TERM_DISP_UP, (grub_uint32_t) GRUB_TERM_DISP_DOWN);
93 grub_printf ("\
94 Press enter to boot the selected OS, \'e\' to edit the\n\
95 commands before booting or \'c\' for a command-line.");
96 if (nested)
97 grub_printf ("\n\
98 ESC to return previous menu.");
103 static grub_menu_entry_t
104 get_entry (grub_menu_t menu, int no)
106 grub_menu_entry_t e;
108 for (e = menu->entry_list; e && no > 0; e = e->next, no--)
111 return e;
114 static void
115 print_entry (int y, int highlight, grub_menu_entry_t entry)
117 int x;
118 const char *title;
119 grub_size_t title_len;
120 grub_ssize_t len;
121 grub_uint32_t *unicode_title;
122 grub_ssize_t i;
123 grub_uint8_t old_color_normal, old_color_highlight;
125 title = entry ? entry->title : "";
126 title_len = grub_strlen (title);
127 unicode_title = grub_malloc (title_len * sizeof (*unicode_title));
128 if (! unicode_title)
129 /* XXX How to show this error? */
130 return;
132 len = grub_utf8_to_ucs4 (unicode_title, title_len,
133 (grub_uint8_t *) title, -1, 0);
134 if (len < 0)
136 /* It is an invalid sequence. */
137 grub_free (unicode_title);
138 return;
141 grub_getcolor (&old_color_normal, &old_color_highlight);
142 grub_setcolor (grub_color_menu_normal, grub_color_menu_highlight);
143 grub_setcolorstate (highlight
144 ? GRUB_TERM_COLOR_HIGHLIGHT
145 : GRUB_TERM_COLOR_NORMAL);
147 grub_gotoxy (GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_MARGIN, y);
149 for (x = GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_MARGIN + 1, i = 0;
150 x < GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_BORDER_WIDTH - GRUB_TERM_MARGIN;
151 i++)
153 if (i < len
154 && x <= (GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_BORDER_WIDTH
155 - GRUB_TERM_MARGIN - 1))
157 grub_ssize_t width;
159 width = grub_getcharwidth (unicode_title[i]);
161 if (x + width > (GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_BORDER_WIDTH
162 - GRUB_TERM_MARGIN - 1))
163 grub_putcode (GRUB_TERM_DISP_RIGHT);
164 else
165 grub_putcode (unicode_title[i]);
167 x += width;
169 else
171 grub_putchar (' ');
172 x++;
175 grub_setcolorstate (GRUB_TERM_COLOR_NORMAL);
176 grub_putchar (' ');
178 grub_gotoxy (GRUB_TERM_CURSOR_X, y);
180 grub_setcolor (old_color_normal, old_color_highlight);
181 grub_setcolorstate (GRUB_TERM_COLOR_NORMAL);
182 grub_free (unicode_title);
185 static void
186 print_entries (grub_menu_t menu, int first, int offset)
188 grub_menu_entry_t e;
189 int i;
191 grub_gotoxy (GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_BORDER_WIDTH,
192 GRUB_TERM_FIRST_ENTRY_Y);
194 if (first)
195 grub_putcode (GRUB_TERM_DISP_UP);
196 else
197 grub_putchar (' ');
199 e = get_entry (menu, first);
201 for (i = 0; i < GRUB_TERM_NUM_ENTRIES; i++)
203 print_entry (GRUB_TERM_FIRST_ENTRY_Y + i, offset == i, e);
204 if (e)
205 e = e->next;
208 grub_gotoxy (GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_BORDER_WIDTH,
209 GRUB_TERM_TOP_BORDER_Y + GRUB_TERM_NUM_ENTRIES);
211 if (e)
212 grub_putcode (GRUB_TERM_DISP_DOWN);
213 else
214 grub_putchar (' ');
216 grub_gotoxy (GRUB_TERM_CURSOR_X, GRUB_TERM_FIRST_ENTRY_Y + offset);
219 /* Initialize the screen. If NESTED is non-zero, assume that this menu
220 is run from another menu or a command-line. If EDIT is non-zero, show
221 a message for the menu entry editor. */
222 void
223 grub_menu_init_page (int nested, int edit)
225 grub_uint8_t old_color_normal, old_color_highlight;
227 grub_getcolor (&old_color_normal, &old_color_highlight);
229 /* By default, use the same colors for the menu. */
230 grub_color_menu_normal = old_color_normal;
231 grub_color_menu_highlight = old_color_highlight;
233 /* Then give user a chance to replace them. */
234 grub_parse_color_name_pair (&grub_color_menu_normal, grub_env_get ("menu_color_normal"));
235 grub_parse_color_name_pair (&grub_color_menu_highlight, grub_env_get ("menu_color_highlight"));
237 grub_normal_init_page ();
238 grub_setcolor (grub_color_menu_normal, grub_color_menu_highlight);
239 draw_border ();
240 grub_setcolor (old_color_normal, old_color_highlight);
241 print_message (nested, edit);
244 /* Return the current timeout. If the variable "timeout" is not set or
245 invalid, return -1. */
246 static int
247 get_timeout (void)
249 char *val;
250 int timeout;
252 val = grub_env_get ("timeout");
253 if (! val)
254 return -1;
256 grub_error_push ();
258 timeout = (int) grub_strtoul (val, 0, 0);
260 /* If the value is invalid, unset the variable. */
261 if (grub_errno != GRUB_ERR_NONE)
263 grub_env_unset ("timeout");
264 grub_errno = GRUB_ERR_NONE;
265 timeout = -1;
268 grub_error_pop ();
270 return timeout;
273 /* Set current timeout in the variable "timeout". */
274 static void
275 set_timeout (int timeout)
277 /* Ignore TIMEOUT if it is zero, because it will be unset really soon. */
278 if (timeout > 0)
280 char buf[16];
282 grub_sprintf (buf, "%d", timeout);
283 grub_env_set ("timeout", buf);
287 /* Get the entry number from the variable NAME. */
288 static int
289 get_entry_number (const char *name)
291 char *val;
292 int entry;
294 val = grub_env_get (name);
295 if (! val)
296 return -1;
298 grub_error_push ();
300 entry = (int) grub_strtoul (val, 0, 0);
302 if (grub_errno != GRUB_ERR_NONE)
304 grub_errno = GRUB_ERR_NONE;
305 entry = -1;
308 grub_error_pop ();
310 return entry;
313 static void
314 print_timeout (int timeout, int offset, int second_stage)
316 /* NOTE: Do not remove the trailing space characters.
317 They are required to clear the line. */
318 char *msg = " The highlighted entry will be booted automatically in %ds. ";
319 char *msg_end = grub_strchr (msg, '%');
321 grub_gotoxy (second_stage ? (msg_end - msg) : 0, GRUB_TERM_HEIGHT - 3);
322 grub_printf (second_stage ? msg_end : msg, timeout);
323 grub_gotoxy (GRUB_TERM_CURSOR_X, GRUB_TERM_FIRST_ENTRY_Y + offset);
324 grub_refresh ();
327 static int
328 run_menu (grub_menu_t menu, int nested)
330 int first, offset;
331 grub_uint64_t saved_time;
332 int default_entry;
333 int timeout;
335 first = 0;
337 default_entry = get_entry_number ("default");
339 /* If DEFAULT_ENTRY is not within the menu entries, fall back to
340 the first entry. */
341 if (default_entry < 0 || default_entry >= menu->size)
342 default_entry = 0;
344 /* If timeout is 0, drawing is pointless (and ugly). */
345 if (get_timeout () == 0)
346 return default_entry;
348 offset = default_entry;
349 if (offset > GRUB_TERM_NUM_ENTRIES - 1)
351 first = offset - (GRUB_TERM_NUM_ENTRIES - 1);
352 offset = GRUB_TERM_NUM_ENTRIES - 1;
355 /* Initialize the time. */
356 saved_time = grub_get_time_ms ();
358 refresh:
359 grub_setcursor (0);
360 grub_menu_init_page (nested, 0);
361 print_entries (menu, first, offset);
362 grub_refresh ();
364 timeout = get_timeout ();
366 if (timeout > 0)
367 print_timeout (timeout, offset, 0);
369 while (1)
371 int c;
372 timeout = get_timeout ();
374 if (timeout > 0)
376 grub_uint64_t current_time;
378 current_time = grub_get_time_ms ();
379 if (current_time - saved_time >= 1000)
381 timeout--;
382 set_timeout (timeout);
383 saved_time = current_time;
384 print_timeout (timeout, offset, 1);
388 if (timeout == 0)
390 grub_env_unset ("timeout");
391 return default_entry;
394 if (grub_checkkey () >= 0 || timeout < 0)
396 c = GRUB_TERM_ASCII_CHAR (grub_getkey ());
398 if (timeout >= 0)
400 grub_gotoxy (0, GRUB_TERM_HEIGHT - 3);
401 grub_printf ("\
403 grub_env_unset ("timeout");
404 grub_env_unset ("fallback");
405 grub_gotoxy (GRUB_TERM_CURSOR_X, GRUB_TERM_FIRST_ENTRY_Y + offset);
408 switch (c)
410 case GRUB_TERM_HOME:
411 first = 0;
412 offset = 0;
413 print_entries (menu, first, offset);
414 break;
416 case GRUB_TERM_END:
417 offset = menu->size - 1;
418 if (offset > GRUB_TERM_NUM_ENTRIES - 1)
420 first = offset - (GRUB_TERM_NUM_ENTRIES - 1);
421 offset = GRUB_TERM_NUM_ENTRIES - 1;
423 print_entries (menu, first, offset);
424 break;
426 case GRUB_TERM_UP:
427 case '^':
428 if (offset > 0)
430 print_entry (GRUB_TERM_FIRST_ENTRY_Y + offset, 0,
431 get_entry (menu, first + offset));
432 offset--;
433 print_entry (GRUB_TERM_FIRST_ENTRY_Y + offset, 1,
434 get_entry (menu, first + offset));
436 else if (first > 0)
438 first--;
439 print_entries (menu, first, offset);
441 break;
443 case GRUB_TERM_DOWN:
444 case 'v':
445 if (menu->size > first + offset + 1)
447 if (offset < GRUB_TERM_NUM_ENTRIES - 1)
449 print_entry (GRUB_TERM_FIRST_ENTRY_Y + offset, 0,
450 get_entry (menu, first + offset));
451 offset++;
452 print_entry (GRUB_TERM_FIRST_ENTRY_Y + offset, 1,
453 get_entry (menu, first + offset));
455 else
457 first++;
458 print_entries (menu, first, offset);
461 break;
463 case GRUB_TERM_PPAGE:
464 if (first == 0)
466 offset = 0;
468 else
470 first -= GRUB_TERM_NUM_ENTRIES;
472 if (first < 0)
474 offset += first;
475 first = 0;
478 print_entries (menu, first, offset);
479 break;
481 case GRUB_TERM_NPAGE:
482 if (offset == 0)
484 offset += GRUB_TERM_NUM_ENTRIES - 1;
485 if (first + offset >= menu->size)
487 offset = menu->size - first - 1;
490 else
492 first += GRUB_TERM_NUM_ENTRIES;
494 if (first + offset >= menu->size)
496 first -= GRUB_TERM_NUM_ENTRIES;
497 offset += GRUB_TERM_NUM_ENTRIES;
499 if (offset > menu->size - 1 ||
500 offset > GRUB_TERM_NUM_ENTRIES - 1)
502 offset = menu->size - first - 1;
504 if (offset > GRUB_TERM_NUM_ENTRIES)
506 first += offset - GRUB_TERM_NUM_ENTRIES + 1;
507 offset = GRUB_TERM_NUM_ENTRIES - 1;
511 print_entries (menu, first, offset);
512 break;
514 case '\n':
515 case '\r':
516 case 6:
517 grub_setcursor (1);
518 return first + offset;
520 case '\e':
521 if (nested)
523 grub_setcursor (1);
524 return -1;
526 break;
528 case 'c':
529 grub_cmdline_run (1);
530 goto refresh;
532 case 'e':
534 grub_menu_entry_t e = get_entry (menu, first + offset);
535 if (e)
536 grub_menu_entry_run (e);
538 goto refresh;
540 default:
541 break;
544 grub_refresh ();
548 /* Never reach here. */
549 return -1;
552 /* Run a menu entry. */
553 static void
554 run_menu_entry (grub_menu_entry_t entry)
556 grub_script_execute (entry->commands);
558 if (grub_errno == GRUB_ERR_NONE && grub_loader_is_loaded ())
559 /* Implicit execution of boot, only if something is loaded. */
560 grub_command_execute ("boot", 0);
563 void
564 grub_menu_run (grub_menu_t menu, int nested)
566 while (1)
568 int boot_entry;
569 grub_menu_entry_t e;
570 int fallback_entry;
572 boot_entry = run_menu (menu, nested);
573 if (boot_entry < 0)
574 break;
576 e = get_entry (menu, boot_entry);
577 if (! e)
578 continue; /* Menu is empty. */
580 grub_cls ();
581 grub_setcursor (1);
583 grub_printf (" Booting \'%s\'\n\n", e->title);
585 run_menu_entry (e);
587 /* Deal with a fallback entry. */
588 /* FIXME: Multiple fallback entries like GRUB Legacy. */
589 fallback_entry = get_entry_number ("fallback");
590 if (fallback_entry >= 0)
592 grub_print_error ();
593 grub_errno = GRUB_ERR_NONE;
595 e = get_entry (menu, fallback_entry);
596 grub_env_unset ("fallback");
597 grub_printf ("\n Falling back to \'%s\'\n\n", e->title);
598 run_menu_entry (e);
601 if (grub_errno != GRUB_ERR_NONE)
603 grub_print_error ();
604 grub_errno = GRUB_ERR_NONE;
606 grub_wait_after_message ();