]> git.proxmox.com Git - grub2.git/blob - normal/menu_text.c
c40fd637432fcec1a3e938fc472d0beee08d01a8
[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_putchar ('\n');
43 grub_printf (_("Press any key to continue..."));
44 (void) grub_getkey ();
45 grub_putchar ('\n');
46 }
47
48 static void
49 print_spaces (int number_spaces)
50 {
51 int i;
52 for (i = 0; i < number_spaces; i++)
53 grub_putchar (' ');
54 }
55
56 static void
57 grub_print_ucs4 (const grub_uint32_t * str,
58 const grub_uint32_t * last_position)
59 {
60 while (str < last_position)
61 {
62 grub_putcode (*str);
63 str++;
64 }
65 }
66
67 static grub_ssize_t
68 getstringwidth (grub_uint32_t * str, const grub_uint32_t * last_position)
69 {
70 grub_ssize_t width = 0;
71
72 while (str < last_position)
73 {
74 width += grub_getcharwidth (*str);
75 str++;
76 }
77 return width;
78 }
79
80 static void
81 print_message_indented (const char *msg)
82 {
83 const int line_len = GRUB_TERM_WIDTH - grub_getcharwidth ('m') * 15;
84
85 grub_uint32_t *unicode_msg;
86
87 grub_ssize_t msg_len = grub_strlen (msg);
88
89 unicode_msg = grub_malloc (msg_len * sizeof (*unicode_msg));
90
91 msg_len = grub_utf8_to_ucs4 (unicode_msg, msg_len,
92 (grub_uint8_t *) msg, -1, 0);
93
94 if (!unicode_msg)
95 {
96 grub_printf ("print_message_indented ERROR1: %s", msg);
97 return;
98 }
99
100 if (msg_len < 0)
101 {
102 grub_printf ("print_message_indented ERROR2: %s", msg);
103 grub_free (unicode_msg);
104 return;
105 }
106
107 const grub_uint32_t *last_position = unicode_msg + msg_len;
108
109 grub_uint32_t *current_position = unicode_msg;
110
111 grub_uint32_t *next_new_line = unicode_msg;
112
113 while (current_position < last_position)
114 {
115 next_new_line = (grub_uint32_t *) last_position;
116
117 while (getstringwidth (current_position, next_new_line) > line_len
118 || (*next_new_line != ' ' && next_new_line > current_position &&
119 next_new_line != last_position))
120 {
121 next_new_line--;
122 }
123
124 if (next_new_line == current_position)
125 {
126 next_new_line = (next_new_line + line_len > last_position) ?
127 (grub_uint32_t *) last_position : next_new_line + line_len;
128 }
129
130 print_spaces (6);
131 grub_print_ucs4 (current_position, next_new_line);
132 grub_putchar ('\n');
133
134 next_new_line++;
135 current_position = next_new_line;
136 }
137 grub_free (unicode_msg);
138 }
139
140
141 static void
142 draw_border (void)
143 {
144 unsigned i;
145
146 grub_setcolorstate (GRUB_TERM_COLOR_NORMAL);
147
148 grub_gotoxy (GRUB_TERM_MARGIN, GRUB_TERM_TOP_BORDER_Y);
149 grub_putcode (GRUB_TERM_DISP_UL);
150 for (i = 0; i < (unsigned) GRUB_TERM_BORDER_WIDTH - 2; i++)
151 grub_putcode (GRUB_TERM_DISP_HLINE);
152 grub_putcode (GRUB_TERM_DISP_UR);
153
154 for (i = 0; i < (unsigned) GRUB_TERM_NUM_ENTRIES; i++)
155 {
156 grub_gotoxy (GRUB_TERM_MARGIN, GRUB_TERM_TOP_BORDER_Y + i + 1);
157 grub_putcode (GRUB_TERM_DISP_VLINE);
158 grub_gotoxy (GRUB_TERM_MARGIN + GRUB_TERM_BORDER_WIDTH - 1,
159 GRUB_TERM_TOP_BORDER_Y + i + 1);
160 grub_putcode (GRUB_TERM_DISP_VLINE);
161 }
162
163 grub_gotoxy (GRUB_TERM_MARGIN,
164 GRUB_TERM_TOP_BORDER_Y + GRUB_TERM_NUM_ENTRIES + 1);
165 grub_putcode (GRUB_TERM_DISP_LL);
166 for (i = 0; i < (unsigned) GRUB_TERM_BORDER_WIDTH - 2; i++)
167 grub_putcode (GRUB_TERM_DISP_HLINE);
168 grub_putcode (GRUB_TERM_DISP_LR);
169
170 grub_setcolorstate (GRUB_TERM_COLOR_NORMAL);
171
172 grub_gotoxy (GRUB_TERM_MARGIN,
173 (GRUB_TERM_TOP_BORDER_Y + GRUB_TERM_NUM_ENTRIES
174 + GRUB_TERM_MARGIN + 1));
175 }
176
177 static void
178 print_message (int nested, int edit)
179 {
180 grub_setcolorstate (GRUB_TERM_COLOR_NORMAL);
181
182 if (edit)
183 {
184 grub_putchar ('\n');
185 print_message_indented (_("Minimum Emacs-like screen editing is \
186 supported. TAB lists completions. Press Ctrl-x to boot, Ctrl-c for a \
187 command-line or ESC to return menu."));
188 }
189 else
190 {
191 const char *msg = _("Use the %C and %C keys to select which \
192 entry is highlighted.");
193 char *msg_translated =
194 grub_malloc (sizeof (char) * grub_strlen (msg) + 1);
195
196 grub_sprintf (msg_translated, msg, (grub_uint32_t) GRUB_TERM_DISP_UP,
197 (grub_uint32_t) GRUB_TERM_DISP_DOWN);
198 grub_putchar ('\n');
199 print_message_indented (msg_translated);
200
201 grub_free (msg_translated);
202
203 print_message_indented (_("Press enter to boot the selected OS, \
204 \'e\' to edit the commands before booting or \'c\' for a command-line."));
205
206 if (nested)
207 {
208 grub_printf ("\n ");
209 grub_printf (_("ESC to return previous menu."));
210 }
211 }
212 }
213
214 static void
215 print_entry (int y, int highlight, grub_menu_entry_t entry)
216 {
217 int x;
218 const char *title;
219 grub_size_t title_len;
220 grub_ssize_t len;
221 grub_uint32_t *unicode_title;
222 grub_ssize_t i;
223 grub_uint8_t old_color_normal, old_color_highlight;
224
225 title = entry ? entry->title : "";
226 title_len = grub_strlen (title);
227 unicode_title = grub_malloc (title_len * sizeof (*unicode_title));
228 if (! unicode_title)
229 /* XXX How to show this error? */
230 return;
231
232 len = grub_utf8_to_ucs4 (unicode_title, title_len,
233 (grub_uint8_t *) title, -1, 0);
234 if (len < 0)
235 {
236 /* It is an invalid sequence. */
237 grub_free (unicode_title);
238 return;
239 }
240
241 grub_getcolor (&old_color_normal, &old_color_highlight);
242 grub_setcolor (grub_color_menu_normal, grub_color_menu_highlight);
243 grub_setcolorstate (highlight
244 ? GRUB_TERM_COLOR_HIGHLIGHT
245 : GRUB_TERM_COLOR_NORMAL);
246
247 grub_gotoxy (GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_MARGIN, y);
248
249 for (x = GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_MARGIN + 1, i = 0;
250 x < GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_BORDER_WIDTH - GRUB_TERM_MARGIN;
251 i++)
252 {
253 if (i < len
254 && x <= (GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_BORDER_WIDTH
255 - GRUB_TERM_MARGIN - 1))
256 {
257 grub_ssize_t width;
258
259 width = grub_getcharwidth (unicode_title[i]);
260
261 if (x + width > (GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_BORDER_WIDTH
262 - GRUB_TERM_MARGIN - 1))
263 grub_putcode (GRUB_TERM_DISP_RIGHT);
264 else
265 grub_putcode (unicode_title[i]);
266
267 x += width;
268 }
269 else
270 {
271 grub_putchar (' ');
272 x++;
273 }
274 }
275 grub_setcolorstate (GRUB_TERM_COLOR_NORMAL);
276 grub_putchar (' ');
277
278 grub_gotoxy (GRUB_TERM_CURSOR_X, y);
279
280 grub_setcolor (old_color_normal, old_color_highlight);
281 grub_setcolorstate (GRUB_TERM_COLOR_NORMAL);
282 grub_free (unicode_title);
283 }
284
285 static void
286 print_entries (grub_menu_t menu, int first, int offset)
287 {
288 grub_menu_entry_t e;
289 int i;
290
291 grub_gotoxy (GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_BORDER_WIDTH,
292 GRUB_TERM_FIRST_ENTRY_Y);
293
294 if (first)
295 grub_putcode (GRUB_TERM_DISP_UP);
296 else
297 grub_putchar (' ');
298
299 e = grub_menu_get_entry (menu, first);
300
301 for (i = 0; i < GRUB_TERM_NUM_ENTRIES; i++)
302 {
303 print_entry (GRUB_TERM_FIRST_ENTRY_Y + i, offset == i, e);
304 if (e)
305 e = e->next;
306 }
307
308 grub_gotoxy (GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_BORDER_WIDTH,
309 GRUB_TERM_TOP_BORDER_Y + GRUB_TERM_NUM_ENTRIES);
310
311 if (e)
312 grub_putcode (GRUB_TERM_DISP_DOWN);
313 else
314 grub_putchar (' ');
315
316 grub_gotoxy (GRUB_TERM_CURSOR_X, GRUB_TERM_FIRST_ENTRY_Y + offset);
317 }
318
319 /* Initialize the screen. If NESTED is non-zero, assume that this menu
320 is run from another menu or a command-line. If EDIT is non-zero, show
321 a message for the menu entry editor. */
322 void
323 grub_menu_init_page (int nested, int edit)
324 {
325 grub_uint8_t old_color_normal, old_color_highlight;
326
327 grub_getcolor (&old_color_normal, &old_color_highlight);
328
329 /* By default, use the same colors for the menu. */
330 grub_color_menu_normal = old_color_normal;
331 grub_color_menu_highlight = old_color_highlight;
332
333 /* Then give user a chance to replace them. */
334 grub_parse_color_name_pair (&grub_color_menu_normal, grub_env_get ("menu_color_normal"));
335 grub_parse_color_name_pair (&grub_color_menu_highlight, grub_env_get ("menu_color_highlight"));
336
337 grub_normal_init_page ();
338 grub_setcolor (grub_color_menu_normal, grub_color_menu_highlight);
339 draw_border ();
340 grub_setcolor (old_color_normal, old_color_highlight);
341 print_message (nested, edit);
342 }
343
344 /* Get the entry number from the variable NAME. */
345 static int
346 get_entry_number (const char *name)
347 {
348 char *val;
349 int entry;
350
351 val = grub_env_get (name);
352 if (! val)
353 return -1;
354
355 grub_error_push ();
356
357 entry = (int) grub_strtoul (val, 0, 0);
358
359 if (grub_errno != GRUB_ERR_NONE)
360 {
361 grub_errno = GRUB_ERR_NONE;
362 entry = -1;
363 }
364
365 grub_error_pop ();
366
367 return entry;
368 }
369
370 static void
371 print_timeout (int timeout, int offset, int second_stage)
372 {
373 const char *msg =
374 _("The highlighted entry will be booted automatically in %ds.");
375 const int msg_localized_len = grub_strlen (msg);
376 const int number_spaces = GRUB_TERM_WIDTH - msg_localized_len - 3;
377
378 char *msg_end = grub_strchr (msg, '%');
379
380 grub_gotoxy (second_stage ? (msg_end - msg + 3) : 3, GRUB_TERM_HEIGHT - 3);
381 grub_printf (second_stage ? msg_end : msg, timeout);
382 print_spaces (second_stage ? number_spaces : 0);
383
384 grub_gotoxy (GRUB_TERM_CURSOR_X, GRUB_TERM_FIRST_ENTRY_Y + offset);
385 grub_refresh ();
386 };
387
388 /* Show the menu and handle menu entry selection. Returns the menu entry
389 index that should be executed or -1 if no entry should be executed (e.g.,
390 Esc pressed to exit a sub-menu or switching menu viewers).
391 If the return value is not -1, then *AUTO_BOOT is nonzero iff the menu
392 entry to be executed is a result of an automatic default selection because
393 of the timeout. */
394 static int
395 run_menu (grub_menu_t menu, int nested, int *auto_boot)
396 {
397 int first, offset;
398 grub_uint64_t saved_time;
399 int default_entry;
400 int timeout;
401
402 first = 0;
403
404 default_entry = get_entry_number ("default");
405
406 /* If DEFAULT_ENTRY is not within the menu entries, fall back to
407 the first entry. */
408 if (default_entry < 0 || default_entry >= menu->size)
409 default_entry = 0;
410
411 /* If timeout is 0, drawing is pointless (and ugly). */
412 if (grub_menu_get_timeout () == 0)
413 {
414 *auto_boot = 1;
415 return default_entry;
416 }
417
418 offset = default_entry;
419 if (offset > GRUB_TERM_NUM_ENTRIES - 1)
420 {
421 first = offset - (GRUB_TERM_NUM_ENTRIES - 1);
422 offset = GRUB_TERM_NUM_ENTRIES - 1;
423 }
424
425 /* Initialize the time. */
426 saved_time = grub_get_time_ms ();
427
428 refresh:
429 grub_setcursor (0);
430 grub_menu_init_page (nested, 0);
431 print_entries (menu, first, offset);
432 grub_refresh ();
433
434 timeout = grub_menu_get_timeout ();
435
436 if (timeout > 0)
437 print_timeout (timeout, offset, 0);
438
439 while (1)
440 {
441 int c;
442 timeout = grub_menu_get_timeout ();
443
444 if (timeout > 0)
445 {
446 grub_uint64_t current_time;
447
448 current_time = grub_get_time_ms ();
449 if (current_time - saved_time >= 1000)
450 {
451 timeout--;
452 grub_menu_set_timeout (timeout);
453 saved_time = current_time;
454 print_timeout (timeout, offset, 1);
455 }
456 }
457
458 if (timeout == 0)
459 {
460 grub_env_unset ("timeout");
461 *auto_boot = 1;
462 return default_entry;
463 }
464
465 if (grub_checkkey () >= 0 || timeout < 0)
466 {
467 c = GRUB_TERM_ASCII_CHAR (grub_getkey ());
468
469 if (timeout >= 0)
470 {
471 grub_gotoxy (0, GRUB_TERM_HEIGHT - 3);
472 print_spaces (GRUB_TERM_WIDTH - 1);
473
474 grub_env_unset ("timeout");
475 grub_env_unset ("fallback");
476 grub_gotoxy (GRUB_TERM_CURSOR_X, GRUB_TERM_FIRST_ENTRY_Y + offset);
477 }
478
479 switch (c)
480 {
481 case GRUB_TERM_HOME:
482 first = 0;
483 offset = 0;
484 print_entries (menu, first, offset);
485 break;
486
487 case GRUB_TERM_END:
488 offset = menu->size - 1;
489 if (offset > GRUB_TERM_NUM_ENTRIES - 1)
490 {
491 first = offset - (GRUB_TERM_NUM_ENTRIES - 1);
492 offset = GRUB_TERM_NUM_ENTRIES - 1;
493 }
494 print_entries (menu, first, offset);
495 break;
496
497 case GRUB_TERM_UP:
498 case '^':
499 if (offset > 0)
500 {
501 print_entry (GRUB_TERM_FIRST_ENTRY_Y + offset, 0,
502 grub_menu_get_entry (menu, first + offset));
503 offset--;
504 print_entry (GRUB_TERM_FIRST_ENTRY_Y + offset, 1,
505 grub_menu_get_entry (menu, first + offset));
506 }
507 else if (first > 0)
508 {
509 first--;
510 print_entries (menu, first, offset);
511 }
512 break;
513
514 case GRUB_TERM_DOWN:
515 case 'v':
516 if (menu->size > first + offset + 1)
517 {
518 if (offset < GRUB_TERM_NUM_ENTRIES - 1)
519 {
520 print_entry (GRUB_TERM_FIRST_ENTRY_Y + offset, 0,
521 grub_menu_get_entry (menu, first + offset));
522 offset++;
523 print_entry (GRUB_TERM_FIRST_ENTRY_Y + offset, 1,
524 grub_menu_get_entry (menu, first + offset));
525 }
526 else
527 {
528 first++;
529 print_entries (menu, first, offset);
530 }
531 }
532 break;
533
534 case GRUB_TERM_PPAGE:
535 if (first == 0)
536 {
537 offset = 0;
538 }
539 else
540 {
541 first -= GRUB_TERM_NUM_ENTRIES;
542
543 if (first < 0)
544 {
545 offset += first;
546 first = 0;
547 }
548 }
549 print_entries (menu, first, offset);
550 break;
551
552 case GRUB_TERM_NPAGE:
553 if (offset == 0)
554 {
555 offset += GRUB_TERM_NUM_ENTRIES - 1;
556 if (first + offset >= menu->size)
557 {
558 offset = menu->size - first - 1;
559 }
560 }
561 else
562 {
563 first += GRUB_TERM_NUM_ENTRIES;
564
565 if (first + offset >= menu->size)
566 {
567 first -= GRUB_TERM_NUM_ENTRIES;
568 offset += GRUB_TERM_NUM_ENTRIES;
569
570 if (offset > menu->size - 1 ||
571 offset > GRUB_TERM_NUM_ENTRIES - 1)
572 {
573 offset = menu->size - first - 1;
574 }
575 if (offset > GRUB_TERM_NUM_ENTRIES)
576 {
577 first += offset - GRUB_TERM_NUM_ENTRIES + 1;
578 offset = GRUB_TERM_NUM_ENTRIES - 1;
579 }
580 }
581 }
582 print_entries (menu, first, offset);
583 break;
584
585 case '\n':
586 case '\r':
587 case 6:
588 grub_setcursor (1);
589 *auto_boot = 0;
590 return first + offset;
591
592 case '\e':
593 if (nested)
594 {
595 grub_setcursor (1);
596 return -1;
597 }
598 break;
599
600 case 'c':
601 grub_cmdline_run (1);
602 goto refresh;
603
604 case 'e':
605 {
606 grub_menu_entry_t e = grub_menu_get_entry (menu, first + offset);
607 if (e)
608 grub_menu_entry_run (e);
609 }
610 goto refresh;
611
612 default:
613 break;
614 }
615
616 grub_refresh ();
617 }
618 }
619
620 /* Never reach here. */
621 return -1;
622 }
623
624 /* Callback invoked immediately before a menu entry is executed. */
625 static void
626 notify_booting (grub_menu_entry_t entry,
627 void *userdata __attribute__((unused)))
628 {
629 grub_printf (" ");
630 grub_printf (_("Booting \'%s\'"), entry->title);
631 grub_printf ("\n\n");
632 }
633
634 /* Callback invoked when a default menu entry executed because of a timeout
635 has failed and an attempt will be made to execute the next fallback
636 entry, ENTRY. */
637 static void
638 notify_fallback (grub_menu_entry_t entry,
639 void *userdata __attribute__((unused)))
640 {
641 grub_printf ("\n ");
642 grub_printf (_("Falling back to \'%s\'"), entry->title);
643 grub_printf ("\n\n");
644 grub_millisleep (DEFAULT_ENTRY_ERROR_DELAY_MS);
645 }
646
647 /* Callback invoked when a menu entry has failed and there is no remaining
648 fallback entry to attempt. */
649 static void
650 notify_execution_failure (void *userdata __attribute__((unused)))
651 {
652 if (grub_errno != GRUB_ERR_NONE)
653 {
654 grub_print_error ();
655 grub_errno = GRUB_ERR_NONE;
656 }
657 grub_printf ("\n ");
658 grub_printf (_("Failed to boot default entries.\n"));
659 grub_wait_after_message ();
660 }
661
662 /* Callbacks used by the text menu to provide user feedback when menu entries
663 are executed. */
664 static struct grub_menu_execute_callback execution_callback =
665 {
666 .notify_booting = notify_booting,
667 .notify_fallback = notify_fallback,
668 .notify_failure = notify_execution_failure
669 };
670
671 static grub_err_t
672 show_text_menu (grub_menu_t menu, int nested)
673 {
674 while (1)
675 {
676 int boot_entry;
677 grub_menu_entry_t e;
678 int auto_boot;
679
680 boot_entry = run_menu (menu, nested, &auto_boot);
681 if (boot_entry < 0)
682 break;
683
684 e = grub_menu_get_entry (menu, boot_entry);
685 if (! e)
686 continue; /* Menu is empty. */
687
688 grub_cls ();
689 grub_setcursor (1);
690
691 if (auto_boot)
692 {
693 grub_menu_execute_with_fallback (menu, e, &execution_callback, 0);
694 }
695 else
696 {
697 grub_errno = GRUB_ERR_NONE;
698 grub_menu_execute_entry (e);
699 if (grub_errno != GRUB_ERR_NONE)
700 {
701 grub_print_error ();
702 grub_errno = GRUB_ERR_NONE;
703 grub_wait_after_message ();
704 }
705 }
706 }
707
708 return GRUB_ERR_NONE;
709 }
710
711 struct grub_menu_viewer grub_normal_text_menu_viewer =
712 {
713 .name = "text",
714 .show_menu = show_text_menu
715 };