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>
29 #include <grub/charset.h>
31 static grub_uint8_t grub_color_menu_normal
;
32 static grub_uint8_t grub_color_menu_highlight
;
34 struct menu_viewer_data
37 struct grub_term_screen_geometry geo
;
42 TIMEOUT_TERSE_NO_MARGIN
45 int *menu_title_offset
;
46 struct grub_term_output
*term
;
50 grub_term_cursor_x (const struct grub_term_screen_geometry
*geo
)
52 return (geo
->first_entry_x
+ geo
->entry_width
);
56 grub_getstringwidth (grub_uint32_t
* str
, const grub_uint32_t
* last_position
,
57 struct grub_term_output
*term
)
59 grub_ssize_t width
= 0;
61 while (str
< last_position
)
63 struct grub_unicode_glyph glyph
;
65 str
+= grub_unicode_aglomerate_comb (str
, last_position
- str
, &glyph
);
66 width
+= grub_term_getcharwidth (term
, &glyph
);
67 grub_unicode_destroy_glyph (&glyph
);
73 grub_print_message_indented_real (const char *msg
, int margin_left
,
75 struct grub_term_output
*term
, int dry_run
)
77 grub_uint32_t
*unicode_msg
;
78 grub_uint32_t
*last_position
;
79 grub_size_t msg_len
= grub_strlen (msg
) + 2;
82 unicode_msg
= grub_malloc (msg_len
* sizeof (grub_uint32_t
));
87 msg_len
= grub_utf8_to_ucs4 (unicode_msg
, msg_len
,
88 (grub_uint8_t
*) msg
, -1, 0);
90 last_position
= unicode_msg
+ msg_len
;
94 ret
= grub_ucs4_count_lines (unicode_msg
, last_position
, margin_left
,
97 grub_print_ucs4_menu (unicode_msg
, last_position
, margin_left
,
98 margin_right
, term
, 0, -1, 0, 0);
100 grub_free (unicode_msg
);
106 grub_print_message_indented (const char *msg
, int margin_left
, int margin_right
,
107 struct grub_term_output
*term
)
109 grub_print_message_indented_real (msg
, margin_left
, margin_right
, term
, 0);
113 draw_border (struct grub_term_output
*term
, const struct grub_term_screen_geometry
*geo
)
117 grub_term_setcolorstate (term
, GRUB_TERM_COLOR_NORMAL
);
119 grub_term_gotoxy (term
, (struct grub_term_coordinate
) { geo
->first_entry_x
- 1,
120 geo
->first_entry_y
- 1 });
121 grub_putcode (GRUB_UNICODE_CORNER_UL
, term
);
122 for (i
= 0; i
< geo
->entry_width
+ 1; i
++)
123 grub_putcode (GRUB_UNICODE_HLINE
, term
);
124 grub_putcode (GRUB_UNICODE_CORNER_UR
, term
);
126 for (i
= 0; i
< geo
->num_entries
; i
++)
128 grub_term_gotoxy (term
, (struct grub_term_coordinate
) { geo
->first_entry_x
- 1,
129 geo
->first_entry_y
+ i
});
130 grub_putcode (GRUB_UNICODE_VLINE
, term
);
131 grub_term_gotoxy (term
,
132 (struct grub_term_coordinate
) { geo
->first_entry_x
+ geo
->entry_width
+ 1,
133 geo
->first_entry_y
+ i
});
134 grub_putcode (GRUB_UNICODE_VLINE
, term
);
137 grub_term_gotoxy (term
,
138 (struct grub_term_coordinate
) { geo
->first_entry_x
- 1,
139 geo
->first_entry_y
- 1 + geo
->num_entries
+ 1 });
140 grub_putcode (GRUB_UNICODE_CORNER_LL
, term
);
141 for (i
= 0; i
< geo
->entry_width
+ 1; i
++)
142 grub_putcode (GRUB_UNICODE_HLINE
, term
);
143 grub_putcode (GRUB_UNICODE_CORNER_LR
, term
);
145 grub_term_setcolorstate (term
, GRUB_TERM_COLOR_NORMAL
);
147 grub_term_gotoxy (term
,
148 (struct grub_term_coordinate
) { geo
->first_entry_x
- 1,
149 (geo
->first_entry_y
- 1 + geo
->num_entries
150 + GRUB_TERM_MARGIN
+ 1) });
154 print_message (int nested
, int edit
, struct grub_term_output
*term
, int dry_run
)
157 grub_term_setcolorstate (term
, GRUB_TERM_COLOR_NORMAL
);
161 ret
+= grub_print_message_indented_real (_("Minimum Emacs-like screen editing is \
162 supported. TAB lists completions. Press Ctrl-x or F10 to boot, Ctrl-c or F2 for a \
163 command-line or ESC to discard edits and return to the GRUB menu."),
164 STANDARD_MARGIN
, STANDARD_MARGIN
,
169 char *msg_translated
;
171 msg_translated
= grub_xasprintf (_("Use the %C and %C keys to select which "
172 "entry is highlighted."),
173 GRUB_UNICODE_UPARROW
,
174 GRUB_UNICODE_DOWNARROW
);
177 ret
+= grub_print_message_indented_real (msg_translated
, STANDARD_MARGIN
,
178 STANDARD_MARGIN
, term
, dry_run
);
180 grub_free (msg_translated
);
185 ret
+= grub_print_message_indented_real
186 (_("Press enter to boot the selected OS, "
187 "`e' to edit the commands before booting "
188 "or `c' for a command-line. ESC to return previous menu."),
189 STANDARD_MARGIN
, STANDARD_MARGIN
, term
, dry_run
);
195 const char *checkret
= grub_env_get("VTOY_CHKDEV_RESULT_STRING");
196 if (checkret
== NULL
|| checkret
[0] != '0') {
197 grub_snprintf(szLine
, sizeof(szLine
), "%s [Unofficial Ventoy]", grub_env_get("VTOY_TEXT_MENU_VER"));
199 grub_snprintf(szLine
, sizeof(szLine
), "%s", grub_env_get("VTOY_TEXT_MENU_VER"));
202 ret
+= grub_print_message_indented_real("\n", STANDARD_MARGIN
, STANDARD_MARGIN
, term
, dry_run
);
204 ret
+= grub_print_message_indented_real(szLine
, STANDARD_MARGIN
, STANDARD_MARGIN
, term
, dry_run
);
206 ret
+= grub_print_message_indented_real("\n", STANDARD_MARGIN
, STANDARD_MARGIN
, term
, dry_run
);
207 ret
+= grub_print_message_indented_real(grub_env_get("VTOY_HOTKEY_TIP"),
208 3, 6, term
, dry_run
);
215 print_entry (int y
, int highlight
, grub_menu_entry_t entry
,
216 const struct menu_viewer_data
*data
)
219 grub_size_t title_len
;
221 grub_uint32_t
*unicode_title
;
223 grub_uint8_t old_color_normal
, old_color_highlight
;
225 title
= entry
? entry
->title
: "";
226 title_len
= grub_strlen (title
);
227 unicode_title
= grub_malloc (title_len
* sizeof (*unicode_title
));
229 /* XXX How to show this error? */
232 len
= grub_utf8_to_ucs4 (unicode_title
, title_len
,
233 (grub_uint8_t
*) title
, -1, 0);
236 /* It is an invalid sequence. */
237 grub_free (unicode_title
);
241 old_color_normal
= grub_term_normal_color
;
242 old_color_highlight
= grub_term_highlight_color
;
243 grub_term_normal_color
= grub_color_menu_normal
;
244 grub_term_highlight_color
= grub_color_menu_highlight
;
245 grub_term_setcolorstate (data
->term
, highlight
246 ? GRUB_TERM_COLOR_HIGHLIGHT
247 : GRUB_TERM_COLOR_NORMAL
);
249 grub_term_gotoxy (data
->term
, (struct grub_term_coordinate
) {
250 data
->geo
.first_entry_x
, y
});
252 for (i
= 0; i
< len
; i
++)
253 if (unicode_title
[i
] == '\n' || unicode_title
[i
] == '\b'
254 || unicode_title
[i
] == '\r' || unicode_title
[i
] == '\e')
255 unicode_title
[i
] = ' ';
257 if (data
->geo
.num_entries
> 1)
258 grub_putcode (highlight
? '*' : ' ', data
->term
);
260 grub_print_ucs4_menu (unicode_title
,
263 data
->geo
.right_margin
,
265 GRUB_UNICODE_RIGHTARROW
, 0);
267 grub_term_setcolorstate (data
->term
, GRUB_TERM_COLOR_NORMAL
);
268 grub_term_gotoxy (data
->term
,
269 (struct grub_term_coordinate
) {
270 grub_term_cursor_x (&data
->geo
), y
});
272 grub_term_normal_color
= old_color_normal
;
273 grub_term_highlight_color
= old_color_highlight
;
275 grub_term_setcolorstate (data
->term
, GRUB_TERM_COLOR_NORMAL
);
276 grub_free (unicode_title
);
280 print_entries (grub_menu_t menu
, const struct menu_viewer_data
*data
)
285 grub_term_gotoxy (data
->term
,
286 (struct grub_term_coordinate
) {
287 data
->geo
.first_entry_x
+ data
->geo
.entry_width
288 + data
->geo
.border
+ 1,
289 data
->geo
.first_entry_y
});
291 if (data
->geo
.num_entries
!= 1)
294 grub_putcode (GRUB_UNICODE_UPARROW
, data
->term
);
296 grub_putcode (' ', data
->term
);
298 e
= grub_menu_get_entry (menu
, data
->first
);
300 for (i
= 0; i
< data
->geo
.num_entries
; i
++)
302 print_entry (data
->geo
.first_entry_y
+ i
, data
->offset
== i
,
308 grub_term_gotoxy (data
->term
,
309 (struct grub_term_coordinate
) { data
->geo
.first_entry_x
+ data
->geo
.entry_width
310 + data
->geo
.border
+ 1,
311 data
->geo
.first_entry_y
+ data
->geo
.num_entries
- 1 });
312 if (data
->geo
.num_entries
== 1)
314 if (data
->first
&& e
)
315 grub_putcode (GRUB_UNICODE_UPDOWNARROW
, data
->term
);
316 else if (data
->first
)
317 grub_putcode (GRUB_UNICODE_UPARROW
, data
->term
);
319 grub_putcode (GRUB_UNICODE_DOWNARROW
, data
->term
);
321 grub_putcode (' ', data
->term
);
326 grub_putcode (GRUB_UNICODE_DOWNARROW
, data
->term
);
328 grub_putcode (' ', data
->term
);
331 grub_term_gotoxy (data
->term
,
332 (struct grub_term_coordinate
) { grub_term_cursor_x (&data
->geo
),
333 data
->geo
.first_entry_y
+ data
->offset
});
336 /* Initialize the screen. If NESTED is non-zero, assume that this menu
337 is run from another menu or a command-line. If EDIT is non-zero, show
338 a message for the menu entry editor. */
340 grub_menu_init_page (int nested
, int edit
,
341 struct grub_term_screen_geometry
*geo
,
342 struct grub_term_output
*term
)
344 grub_uint8_t old_color_normal
, old_color_highlight
;
346 int bottom_message
= 1;
351 geo
->first_entry_x
= 1 /* margin */ + 1 /* border */;
352 geo
->entry_width
= grub_term_width (term
) - 5;
354 geo
->first_entry_y
= 2 /* two empty lines*/
355 + 1 /* GNU GRUB version text */ + 1 /* top border */;
357 geo
->timeout_lines
= 2;
359 /* 3 lines for timeout message and bottom margin. 2 lines for the border. */
360 geo
->num_entries
= grub_term_height (term
) - geo
->first_entry_y
361 - 1 /* bottom border */
362 - 1 /* empty line before info message*/
363 - geo
->timeout_lines
/* timeout */
364 - 1 /* empty final line */;
365 msg_num_lines
= print_message (nested
, edit
, term
, 1);
366 if (geo
->num_entries
- msg_num_lines
< 3
367 || geo
->entry_width
< 10)
369 geo
->num_entries
+= 4;
370 geo
->first_entry_y
-= 2;
372 geo
->first_entry_x
-= 1;
373 geo
->entry_width
+= 1;
375 if (geo
->num_entries
- msg_num_lines
< 3
376 || geo
->entry_width
< 10)
378 geo
->num_entries
+= 2;
379 geo
->first_entry_y
-= 1;
380 geo
->first_entry_x
-= 1;
381 geo
->entry_width
+= 2;
385 if (geo
->entry_width
<= 0)
386 geo
->entry_width
= 1;
388 if (geo
->num_entries
- msg_num_lines
< 3
389 && geo
->timeout_lines
== 2)
391 geo
->timeout_lines
= 1;
395 if (geo
->num_entries
- msg_num_lines
< 3)
397 geo
->num_entries
+= 1;
398 geo
->first_entry_y
-= 1;
402 if (geo
->num_entries
- msg_num_lines
>= 2)
403 geo
->num_entries
-= msg_num_lines
;
407 /* By default, use the same colors for the menu. */
408 old_color_normal
= grub_term_normal_color
;
409 old_color_highlight
= grub_term_highlight_color
;
410 grub_color_menu_normal
= grub_term_normal_color
;
411 grub_color_menu_highlight
= grub_term_highlight_color
;
413 /* Then give user a chance to replace them. */
414 grub_parse_color_name_pair (&grub_color_menu_normal
,
415 grub_env_get ("menu_color_normal"));
416 grub_parse_color_name_pair (&grub_color_menu_highlight
,
417 grub_env_get ("menu_color_highlight"));
420 grub_normal_init_page (term
, empty_lines
);
422 grub_term_cls (term
);
424 grub_term_normal_color
= grub_color_menu_normal
;
425 grub_term_highlight_color
= grub_color_menu_highlight
;
427 draw_border (term
, geo
);
428 grub_term_normal_color
= old_color_normal
;
429 grub_term_highlight_color
= old_color_highlight
;
430 geo
->timeout_y
= geo
->first_entry_y
+ geo
->num_entries
431 + geo
->border
+ empty_lines
;
434 grub_term_gotoxy (term
,
435 (struct grub_term_coordinate
) { GRUB_TERM_MARGIN
,
438 print_message (nested
, edit
, term
, 0);
439 geo
->timeout_y
+= msg_num_lines
;
441 geo
->right_margin
= grub_term_width (term
)
443 - geo
->entry_width
- 1;
447 menu_text_print_timeout (int timeout
, void *dataptr
)
449 struct menu_viewer_data
*data
= dataptr
;
450 char *msg_translated
= 0;
452 grub_term_gotoxy (data
->term
,
453 (struct grub_term_coordinate
) { 0, data
->geo
.timeout_y
});
455 if (data
->timeout_msg
== TIMEOUT_TERSE
456 || data
->timeout_msg
== TIMEOUT_TERSE_NO_MARGIN
)
457 msg_translated
= grub_xasprintf (_("%ds"), timeout
);
459 msg_translated
= grub_xasprintf (_("The highlighted entry will be executed automatically in %ds."), timeout
);
463 grub_errno
= GRUB_ERR_NONE
;
467 if (data
->timeout_msg
== TIMEOUT_UNKNOWN
)
469 data
->timeout_msg
= grub_print_message_indented_real (msg_translated
,
471 <= data
->geo
.timeout_lines
? TIMEOUT_NORMAL
: TIMEOUT_TERSE
;
472 if (data
->timeout_msg
== TIMEOUT_TERSE
)
474 grub_free (msg_translated
);
475 msg_translated
= grub_xasprintf (_("%ds"), timeout
);
476 if (grub_term_width (data
->term
) < 10)
477 data
->timeout_msg
= TIMEOUT_TERSE_NO_MARGIN
;
481 grub_print_message_indented (msg_translated
,
482 data
->timeout_msg
== TIMEOUT_TERSE_NO_MARGIN
? 0 : 3,
483 data
->timeout_msg
== TIMEOUT_TERSE_NO_MARGIN
? 0 : 1,
485 grub_free (msg_translated
);
487 grub_term_gotoxy (data
->term
,
488 (struct grub_term_coordinate
) {
489 grub_term_cursor_x (&data
->geo
),
490 data
->geo
.first_entry_y
+ data
->offset
});
491 grub_term_refresh (data
->term
);
495 menu_text_set_chosen_entry (int entry
, void *dataptr
)
497 struct menu_viewer_data
*data
= dataptr
;
498 int oldoffset
= data
->offset
;
499 int complete_redraw
= 0;
501 data
->offset
= entry
- data
->first
;
502 if (data
->offset
> data
->geo
.num_entries
- 1)
504 data
->first
= entry
- (data
->geo
.num_entries
- 1);
505 data
->offset
= data
->geo
.num_entries
- 1;
508 if (data
->offset
< 0)
515 print_entries (data
->menu
, data
);
518 print_entry (data
->geo
.first_entry_y
+ oldoffset
, 0,
519 grub_menu_get_entry (data
->menu
, data
->first
+ oldoffset
),
521 print_entry (data
->geo
.first_entry_y
+ data
->offset
, 1,
522 grub_menu_get_entry (data
->menu
, data
->first
+ data
->offset
),
525 grub_term_refresh (data
->term
);
529 menu_text_scroll_chosen_entry (void *dataptr
, int diren
)
531 struct menu_viewer_data
*data
= dataptr
;
532 const char *orig_title
, *scrolled_title
;
535 grub_menu_entry_t entry
;
537 if (!data
->menu
->size
)
540 selected
= data
->first
+ data
->offset
;
541 entry
= grub_menu_get_entry (data
->menu
, selected
);
542 orig_title
= entry
->title
;
543 off
= data
->menu_title_offset
[selected
] + diren
;
545 || off
> grub_utf8_get_num_code (orig_title
, grub_strlen(orig_title
)))
549 grub_utf8_offset_code (orig_title
, grub_strlen (orig_title
), off
);
551 entry
->title
= scrolled_title
;
552 print_entry (data
->geo
.first_entry_y
+ data
->offset
, 1, entry
, data
);
554 entry
->title
= orig_title
;
555 data
->menu_title_offset
[selected
] = off
;
556 grub_term_refresh (data
->term
);
560 menu_text_fini (void *dataptr
)
562 struct menu_viewer_data
*data
= dataptr
;
564 grub_term_setcursor (data
->term
, 1);
565 grub_term_cls (data
->term
);
566 if (data
->menu_title_offset
)
567 grub_free (data
->menu_title_offset
);
572 menu_text_clear_timeout (void *dataptr
)
574 struct menu_viewer_data
*data
= dataptr
;
577 for (i
= 0; i
< data
->geo
.timeout_lines
;i
++)
579 grub_term_gotoxy (data
->term
, (struct grub_term_coordinate
) {
580 0, data
->geo
.timeout_y
+ i
});
581 grub_print_spaces (data
->term
, grub_term_width (data
->term
) - 1);
583 if (data
->geo
.num_entries
<= 5 && !data
->geo
.border
)
585 grub_term_gotoxy (data
->term
,
586 (struct grub_term_coordinate
) {
587 data
->geo
.first_entry_x
+ data
->geo
.entry_width
588 + data
->geo
.border
+ 1,
589 data
->geo
.first_entry_y
+ data
->geo
.num_entries
- 1
591 grub_putcode (' ', data
->term
);
593 data
->geo
.timeout_lines
= 0;
594 data
->geo
.num_entries
++;
595 print_entries (data
->menu
, data
);
597 grub_term_gotoxy (data
->term
,
598 (struct grub_term_coordinate
) {
599 grub_term_cursor_x (&data
->geo
),
600 data
->geo
.first_entry_y
+ data
->offset
});
601 grub_term_refresh (data
->term
);
605 grub_menu_try_text (struct grub_term_output
*term
,
606 int entry
, grub_menu_t menu
, int nested
)
608 struct menu_viewer_data
*data
;
609 struct grub_menu_viewer
*instance
;
611 instance
= grub_zalloc (sizeof (*instance
));
615 data
= grub_zalloc (sizeof (*data
));
618 grub_free (instance
);
623 data
->menu_title_offset
= grub_zalloc (sizeof (*data
->menu_title_offset
) * menu
->size
);
626 instance
->data
= data
;
627 instance
->set_chosen_entry
= menu_text_set_chosen_entry
;
628 if (data
->menu_title_offset
)
629 instance
->scroll_chosen_entry
= menu_text_scroll_chosen_entry
;
630 instance
->print_timeout
= menu_text_print_timeout
;
631 instance
->clear_timeout
= menu_text_clear_timeout
;
632 instance
->fini
= menu_text_fini
;
636 data
->offset
= entry
;
639 grub_term_setcursor (data
->term
, 0);
640 grub_menu_init_page (nested
, 0, &data
->geo
, data
->term
);
642 if (data
->offset
> data
->geo
.num_entries
- 1)
644 data
->first
= data
->offset
- (data
->geo
.num_entries
- 1);
645 data
->offset
= data
->geo
.num_entries
- 1;
648 print_entries (menu
, data
);
649 grub_term_refresh (data
->term
);
650 grub_menu_register_viewer (instance
);
652 return GRUB_ERR_NONE
;