]> git.proxmox.com Git - grub2.git/blob - normal/menu_text.c
mainstream merged into pci
[grub2.git] / normal / menu_text.c
1 /* menu_text.c - Basic text menu implementation. */
2 /*
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2003,2004,2005,2006,2007,2008,2009 Free Software Foundation, Inc.
5 *
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.
10 *
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.
15 *
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/>.
18 */
19
20 #include <grub/normal.h>
21 #include <grub/term.h>
22 #include <grub/misc.h>
23 #include <grub/loader.h>
24 #include <grub/mm.h>
25 #include <grub/time.h>
26 #include <grub/env.h>
27 #include <grub/menu_viewer.h>
28 #include <grub/i18n.h>
29
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
33
34 static grub_uint8_t grub_color_menu_normal;
35 static grub_uint8_t grub_color_menu_highlight;
36
37 /* Wait until the user pushes any key so that the user
38 can see what happened. */
39 void
40 grub_wait_after_message (void)
41 {
42 grub_printf ("\nPress any key to continue...");
43 (void) grub_getkey ();
44 grub_putchar ('\n');
45 }
46
47 static void
48 draw_border (void)
49 {
50 unsigned i;
51
52 grub_setcolorstate (GRUB_TERM_COLOR_NORMAL);
53
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);
59
60 for (i = 0; i < (unsigned) GRUB_TERM_NUM_ENTRIES; i++)
61 {
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);
67 }
68
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);
75
76 grub_setcolorstate (GRUB_TERM_COLOR_NORMAL);
77
78 grub_gotoxy (GRUB_TERM_MARGIN,
79 (GRUB_TERM_TOP_BORDER_Y + GRUB_TERM_NUM_ENTRIES
80 + GRUB_TERM_MARGIN + 1));
81 }
82
83 static void
84 print_message (int nested, int edit)
85 {
86 grub_setcolorstate (GRUB_TERM_COLOR_NORMAL);
87
88 if (edit)
89 {
90 grub_printf ("\n\
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.");
94 }
95 else
96 {
97 grub_printf (_("\n\
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);
100 grub_printf ("\
101 Press enter to boot the selected OS, \'e\' to edit the\n\
102 commands before booting or \'c\' for a command-line.");
103 if (nested)
104 grub_printf ("\n\
105 ESC to return previous menu.");
106 }
107 }
108
109 static void
110 print_entry (int y, int highlight, grub_menu_entry_t entry)
111 {
112 int x;
113 const char *title;
114 grub_size_t title_len;
115 grub_ssize_t len;
116 grub_uint32_t *unicode_title;
117 grub_ssize_t i;
118 grub_uint8_t old_color_normal, old_color_highlight;
119
120 title = entry ? entry->title : "";
121 title_len = grub_strlen (title);
122 unicode_title = grub_malloc (title_len * sizeof (*unicode_title));
123 if (! unicode_title)
124 /* XXX How to show this error? */
125 return;
126
127 len = grub_utf8_to_ucs4 (unicode_title, title_len,
128 (grub_uint8_t *) title, -1, 0);
129 if (len < 0)
130 {
131 /* It is an invalid sequence. */
132 grub_free (unicode_title);
133 return;
134 }
135
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);
141
142 grub_gotoxy (GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_MARGIN, y);
143
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;
146 i++)
147 {
148 if (i < len
149 && x <= (GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_BORDER_WIDTH
150 - GRUB_TERM_MARGIN - 1))
151 {
152 grub_ssize_t width;
153
154 width = grub_getcharwidth (unicode_title[i]);
155
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);
159 else
160 grub_putcode (unicode_title[i]);
161
162 x += width;
163 }
164 else
165 {
166 grub_putchar (' ');
167 x++;
168 }
169 }
170 grub_setcolorstate (GRUB_TERM_COLOR_NORMAL);
171 grub_putchar (' ');
172
173 grub_gotoxy (GRUB_TERM_CURSOR_X, y);
174
175 grub_setcolor (old_color_normal, old_color_highlight);
176 grub_setcolorstate (GRUB_TERM_COLOR_NORMAL);
177 grub_free (unicode_title);
178 }
179
180 static void
181 print_entries (grub_menu_t menu, int first, int offset)
182 {
183 grub_menu_entry_t e;
184 int i;
185
186 grub_gotoxy (GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_BORDER_WIDTH,
187 GRUB_TERM_FIRST_ENTRY_Y);
188
189 if (first)
190 grub_putcode (GRUB_TERM_DISP_UP);
191 else
192 grub_putchar (' ');
193
194 e = grub_menu_get_entry (menu, first);
195
196 for (i = 0; i < GRUB_TERM_NUM_ENTRIES; i++)
197 {
198 print_entry (GRUB_TERM_FIRST_ENTRY_Y + i, offset == i, e);
199 if (e)
200 e = e->next;
201 }
202
203 grub_gotoxy (GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_BORDER_WIDTH,
204 GRUB_TERM_TOP_BORDER_Y + GRUB_TERM_NUM_ENTRIES);
205
206 if (e)
207 grub_putcode (GRUB_TERM_DISP_DOWN);
208 else
209 grub_putchar (' ');
210
211 grub_gotoxy (GRUB_TERM_CURSOR_X, GRUB_TERM_FIRST_ENTRY_Y + offset);
212 }
213
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. */
217 void
218 grub_menu_init_page (int nested, int edit)
219 {
220 grub_uint8_t old_color_normal, old_color_highlight;
221
222 grub_getcolor (&old_color_normal, &old_color_highlight);
223
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;
227
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"));
231
232 grub_normal_init_page ();
233 grub_setcolor (grub_color_menu_normal, grub_color_menu_highlight);
234 draw_border ();
235 grub_setcolor (old_color_normal, old_color_highlight);
236 print_message (nested, edit);
237 }
238
239 /* Get the entry number from the variable NAME. */
240 static int
241 get_entry_number (const char *name)
242 {
243 char *val;
244 int entry;
245
246 val = grub_env_get (name);
247 if (! val)
248 return -1;
249
250 grub_error_push ();
251
252 entry = (int) grub_strtoul (val, 0, 0);
253
254 if (grub_errno != GRUB_ERR_NONE)
255 {
256 grub_errno = GRUB_ERR_NONE;
257 entry = -1;
258 }
259
260 grub_error_pop ();
261
262 return entry;
263 }
264
265 static void
266 print_timeout (int timeout, int offset, int second_stage)
267 {
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, '%');
272
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);
276 grub_refresh ();
277 };
278
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
284 of the timeout. */
285 static int
286 run_menu (grub_menu_t menu, int nested, int *auto_boot)
287 {
288 int first, offset;
289 grub_uint64_t saved_time;
290 int default_entry;
291 int timeout;
292
293 first = 0;
294
295 default_entry = get_entry_number ("default");
296
297 /* If DEFAULT_ENTRY is not within the menu entries, fall back to
298 the first entry. */
299 if (default_entry < 0 || default_entry >= menu->size)
300 default_entry = 0;
301
302 /* If timeout is 0, drawing is pointless (and ugly). */
303 if (grub_menu_get_timeout () == 0)
304 {
305 *auto_boot = 1;
306 return default_entry;
307 }
308
309 offset = default_entry;
310 if (offset > GRUB_TERM_NUM_ENTRIES - 1)
311 {
312 first = offset - (GRUB_TERM_NUM_ENTRIES - 1);
313 offset = GRUB_TERM_NUM_ENTRIES - 1;
314 }
315
316 /* Initialize the time. */
317 saved_time = grub_get_time_ms ();
318
319 refresh:
320 grub_setcursor (0);
321 grub_menu_init_page (nested, 0);
322 print_entries (menu, first, offset);
323 grub_refresh ();
324
325 timeout = grub_menu_get_timeout ();
326
327 if (timeout > 0)
328 print_timeout (timeout, offset, 0);
329
330 while (1)
331 {
332 int c;
333 timeout = grub_menu_get_timeout ();
334
335 if (timeout > 0)
336 {
337 grub_uint64_t current_time;
338
339 current_time = grub_get_time_ms ();
340 if (current_time - saved_time >= 1000)
341 {
342 timeout--;
343 grub_menu_set_timeout (timeout);
344 saved_time = current_time;
345 print_timeout (timeout, offset, 1);
346 }
347 }
348
349 if (timeout == 0)
350 {
351 grub_env_unset ("timeout");
352 *auto_boot = 1;
353 return default_entry;
354 }
355
356 if (grub_checkkey () >= 0 || timeout < 0)
357 {
358 c = GRUB_TERM_ASCII_CHAR (grub_getkey ());
359
360 if (timeout >= 0)
361 {
362 grub_gotoxy (0, GRUB_TERM_HEIGHT - 3);
363 grub_printf ("\
364 ");
365 grub_env_unset ("timeout");
366 grub_env_unset ("fallback");
367 grub_gotoxy (GRUB_TERM_CURSOR_X, GRUB_TERM_FIRST_ENTRY_Y + offset);
368 }
369
370 switch (c)
371 {
372 case GRUB_TERM_HOME:
373 first = 0;
374 offset = 0;
375 print_entries (menu, first, offset);
376 break;
377
378 case GRUB_TERM_END:
379 offset = menu->size - 1;
380 if (offset > GRUB_TERM_NUM_ENTRIES - 1)
381 {
382 first = offset - (GRUB_TERM_NUM_ENTRIES - 1);
383 offset = GRUB_TERM_NUM_ENTRIES - 1;
384 }
385 print_entries (menu, first, offset);
386 break;
387
388 case GRUB_TERM_UP:
389 case '^':
390 if (offset > 0)
391 {
392 print_entry (GRUB_TERM_FIRST_ENTRY_Y + offset, 0,
393 grub_menu_get_entry (menu, first + offset));
394 offset--;
395 print_entry (GRUB_TERM_FIRST_ENTRY_Y + offset, 1,
396 grub_menu_get_entry (menu, first + offset));
397 }
398 else if (first > 0)
399 {
400 first--;
401 print_entries (menu, first, offset);
402 }
403 break;
404
405 case GRUB_TERM_DOWN:
406 case 'v':
407 if (menu->size > first + offset + 1)
408 {
409 if (offset < GRUB_TERM_NUM_ENTRIES - 1)
410 {
411 print_entry (GRUB_TERM_FIRST_ENTRY_Y + offset, 0,
412 grub_menu_get_entry (menu, first + offset));
413 offset++;
414 print_entry (GRUB_TERM_FIRST_ENTRY_Y + offset, 1,
415 grub_menu_get_entry (menu, first + offset));
416 }
417 else
418 {
419 first++;
420 print_entries (menu, first, offset);
421 }
422 }
423 break;
424
425 case GRUB_TERM_PPAGE:
426 if (first == 0)
427 {
428 offset = 0;
429 }
430 else
431 {
432 first -= GRUB_TERM_NUM_ENTRIES;
433
434 if (first < 0)
435 {
436 offset += first;
437 first = 0;
438 }
439 }
440 print_entries (menu, first, offset);
441 break;
442
443 case GRUB_TERM_NPAGE:
444 if (offset == 0)
445 {
446 offset += GRUB_TERM_NUM_ENTRIES - 1;
447 if (first + offset >= menu->size)
448 {
449 offset = menu->size - first - 1;
450 }
451 }
452 else
453 {
454 first += GRUB_TERM_NUM_ENTRIES;
455
456 if (first + offset >= menu->size)
457 {
458 first -= GRUB_TERM_NUM_ENTRIES;
459 offset += GRUB_TERM_NUM_ENTRIES;
460
461 if (offset > menu->size - 1 ||
462 offset > GRUB_TERM_NUM_ENTRIES - 1)
463 {
464 offset = menu->size - first - 1;
465 }
466 if (offset > GRUB_TERM_NUM_ENTRIES)
467 {
468 first += offset - GRUB_TERM_NUM_ENTRIES + 1;
469 offset = GRUB_TERM_NUM_ENTRIES - 1;
470 }
471 }
472 }
473 print_entries (menu, first, offset);
474 break;
475
476 case '\n':
477 case '\r':
478 case 6:
479 grub_setcursor (1);
480 *auto_boot = 0;
481 return first + offset;
482
483 case '\e':
484 if (nested)
485 {
486 grub_setcursor (1);
487 return -1;
488 }
489 break;
490
491 case 'c':
492 grub_cmdline_run (1);
493 goto refresh;
494
495 case 'e':
496 {
497 grub_menu_entry_t e = grub_menu_get_entry (menu, first + offset);
498 if (e)
499 grub_menu_entry_run (e);
500 }
501 goto refresh;
502
503 default:
504 break;
505 }
506
507 grub_refresh ();
508 }
509 }
510
511 /* Never reach here. */
512 return -1;
513 }
514
515 /* Callback invoked immediately before a menu entry is executed. */
516 static void
517 notify_booting (grub_menu_entry_t entry,
518 void *userdata __attribute__((unused)))
519 {
520 grub_printf (" Booting \'%s\'\n\n", entry->title);
521 }
522
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
525 entry, ENTRY. */
526 static void
527 notify_fallback (grub_menu_entry_t entry,
528 void *userdata __attribute__((unused)))
529 {
530 grub_printf ("\n Falling back to \'%s\'\n\n", entry->title);
531 grub_millisleep (DEFAULT_ENTRY_ERROR_DELAY_MS);
532 }
533
534 /* Callback invoked when a menu entry has failed and there is no remaining
535 fallback entry to attempt. */
536 static void
537 notify_execution_failure (void *userdata __attribute__((unused)))
538 {
539 if (grub_errno != GRUB_ERR_NONE)
540 {
541 grub_print_error ();
542 grub_errno = GRUB_ERR_NONE;
543 }
544 grub_printf ("\n Failed to boot default entries.\n");
545 grub_wait_after_message ();
546 }
547
548 /* Callbacks used by the text menu to provide user feedback when menu entries
549 are executed. */
550 static struct grub_menu_execute_callback execution_callback =
551 {
552 .notify_booting = notify_booting,
553 .notify_fallback = notify_fallback,
554 .notify_failure = notify_execution_failure
555 };
556
557 static grub_err_t
558 show_text_menu (grub_menu_t menu, int nested)
559 {
560 while (1)
561 {
562 int boot_entry;
563 grub_menu_entry_t e;
564 int auto_boot;
565
566 boot_entry = run_menu (menu, nested, &auto_boot);
567 if (boot_entry < 0)
568 break;
569
570 e = grub_menu_get_entry (menu, boot_entry);
571 if (! e)
572 continue; /* Menu is empty. */
573
574 grub_cls ();
575 grub_setcursor (1);
576
577 if (auto_boot)
578 {
579 grub_menu_execute_with_fallback (menu, e, &execution_callback, 0);
580 }
581 else
582 {
583 grub_errno = GRUB_ERR_NONE;
584 grub_menu_execute_entry (e);
585 if (grub_errno != GRUB_ERR_NONE)
586 {
587 grub_print_error ();
588 grub_errno = GRUB_ERR_NONE;
589 grub_wait_after_message ();
590 }
591 }
592 }
593
594 return GRUB_ERR_NONE;
595 }
596
597 struct grub_menu_viewer grub_normal_text_menu_viewer =
598 {
599 .name = "text",
600 .show_menu = show_text_menu
601 };