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 struct grub_term_output
*term
;
49 grub_term_cursor_x (const struct grub_term_screen_geometry
*geo
)
51 return (geo
->first_entry_x
+ geo
->entry_width
);
55 grub_getstringwidth (grub_uint32_t
* str
, const grub_uint32_t
* last_position
,
56 struct grub_term_output
*term
)
58 grub_ssize_t width
= 0;
60 while (str
< last_position
)
62 struct grub_unicode_glyph glyph
;
64 str
+= grub_unicode_aglomerate_comb (str
, last_position
- str
, &glyph
);
65 width
+= grub_term_getcharwidth (term
, &glyph
);
66 grub_unicode_destroy_glyph (&glyph
);
72 grub_print_message_indented_real (const char *msg
, int margin_left
,
74 struct grub_term_output
*term
, int dry_run
)
76 grub_uint32_t
*unicode_msg
;
77 grub_uint32_t
*last_position
;
78 grub_size_t msg_len
= grub_strlen (msg
) + 2;
81 unicode_msg
= grub_malloc (msg_len
* sizeof (grub_uint32_t
));
86 msg_len
= grub_utf8_to_ucs4 (unicode_msg
, msg_len
,
87 (grub_uint8_t
*) msg
, -1, 0);
89 last_position
= unicode_msg
+ msg_len
;
93 ret
= grub_ucs4_count_lines (unicode_msg
, last_position
, margin_left
,
96 grub_print_ucs4_menu (unicode_msg
, last_position
, margin_left
,
97 margin_right
, term
, 0, -1, 0, 0);
99 grub_free (unicode_msg
);
105 grub_print_message_indented (const char *msg
, int margin_left
, int margin_right
,
106 struct grub_term_output
*term
)
108 grub_print_message_indented_real (msg
, margin_left
, margin_right
, term
, 0);
112 draw_border (struct grub_term_output
*term
, const struct grub_term_screen_geometry
*geo
)
116 grub_term_setcolorstate (term
, GRUB_TERM_COLOR_NORMAL
);
118 grub_term_gotoxy (term
, (struct grub_term_coordinate
) { geo
->first_entry_x
- 1,
119 geo
->first_entry_y
- 1 });
120 grub_putcode (GRUB_UNICODE_CORNER_UL
, term
);
121 for (i
= 0; i
< geo
->entry_width
+ 1; i
++)
122 grub_putcode (GRUB_UNICODE_HLINE
, term
);
123 grub_putcode (GRUB_UNICODE_CORNER_UR
, term
);
125 for (i
= 0; i
< geo
->num_entries
; i
++)
127 grub_term_gotoxy (term
, (struct grub_term_coordinate
) { geo
->first_entry_x
- 1,
128 geo
->first_entry_y
+ i
});
129 grub_putcode (GRUB_UNICODE_VLINE
, term
);
130 grub_term_gotoxy (term
,
131 (struct grub_term_coordinate
) { geo
->first_entry_x
+ geo
->entry_width
+ 1,
132 geo
->first_entry_y
+ i
});
133 grub_putcode (GRUB_UNICODE_VLINE
, term
);
136 grub_term_gotoxy (term
,
137 (struct grub_term_coordinate
) { geo
->first_entry_x
- 1,
138 geo
->first_entry_y
- 1 + geo
->num_entries
+ 1 });
139 grub_putcode (GRUB_UNICODE_CORNER_LL
, term
);
140 for (i
= 0; i
< geo
->entry_width
+ 1; i
++)
141 grub_putcode (GRUB_UNICODE_HLINE
, term
);
142 grub_putcode (GRUB_UNICODE_CORNER_LR
, term
);
144 grub_term_setcolorstate (term
, GRUB_TERM_COLOR_NORMAL
);
146 grub_term_gotoxy (term
,
147 (struct grub_term_coordinate
) { geo
->first_entry_x
- 1,
148 (geo
->first_entry_y
- 1 + geo
->num_entries
149 + GRUB_TERM_MARGIN
+ 1) });
153 print_message (int nested
, int edit
, struct grub_term_output
*term
, int dry_run
)
156 grub_term_setcolorstate (term
, GRUB_TERM_COLOR_NORMAL
);
160 ret
+= grub_print_message_indented_real (_("Minimum Emacs-like screen editing is \
161 supported. TAB lists completions. Press Ctrl-x or F10 to boot, Ctrl-c or F2 for a \
162 command-line or ESC to discard edits and return to the GRUB menu."),
163 STANDARD_MARGIN
, STANDARD_MARGIN
,
168 char *msg_translated
;
170 msg_translated
= grub_xasprintf (_("Use the %C and %C keys to select which "
171 "entry is highlighted."),
172 GRUB_UNICODE_UPARROW
,
173 GRUB_UNICODE_DOWNARROW
);
176 ret
+= grub_print_message_indented_real (msg_translated
, STANDARD_MARGIN
,
177 STANDARD_MARGIN
, term
, dry_run
);
179 grub_free (msg_translated
);
184 ret
+= grub_print_message_indented_real
185 (_("Press enter to boot the selected OS, "
186 "`e' to edit the commands before booting "
187 "or `c' for a command-line. ESC to return previous menu."),
188 STANDARD_MARGIN
, STANDARD_MARGIN
, term
, dry_run
);
194 const char *checkret
= grub_env_get("VTOY_CHKDEV_RESULT_STRING");
195 if (checkret
== NULL
|| checkret
[0] != '0') {
196 grub_snprintf(szLine
, sizeof(szLine
), "%s [Unofficial Ventoy]", grub_env_get("VTOY_TEXT_MENU_VER"));
198 grub_snprintf(szLine
, sizeof(szLine
), "%s", grub_env_get("VTOY_TEXT_MENU_VER"));
201 ret
+= grub_print_message_indented_real("\n", STANDARD_MARGIN
, STANDARD_MARGIN
, term
, dry_run
);
203 ret
+= grub_print_message_indented_real(szLine
, STANDARD_MARGIN
, STANDARD_MARGIN
, term
, dry_run
);
205 ret
+= grub_print_message_indented_real("\n", STANDARD_MARGIN
, STANDARD_MARGIN
, term
, dry_run
);
206 ret
+= grub_print_message_indented_real(grub_env_get("VTOY_HOTKEY_TIP"),
207 3, 6, term
, dry_run
);
214 print_entry (int y
, int highlight
, grub_menu_entry_t entry
,
215 const struct menu_viewer_data
*data
)
218 grub_size_t title_len
;
220 grub_uint32_t
*unicode_title
;
222 grub_uint8_t old_color_normal
, old_color_highlight
;
224 title
= entry
? entry
->title
: "";
225 title_len
= grub_strlen (title
);
226 unicode_title
= grub_malloc (title_len
* sizeof (*unicode_title
));
228 /* XXX How to show this error? */
231 len
= grub_utf8_to_ucs4 (unicode_title
, title_len
,
232 (grub_uint8_t
*) title
, -1, 0);
235 /* It is an invalid sequence. */
236 grub_free (unicode_title
);
240 old_color_normal
= grub_term_normal_color
;
241 old_color_highlight
= grub_term_highlight_color
;
242 grub_term_normal_color
= grub_color_menu_normal
;
243 grub_term_highlight_color
= grub_color_menu_highlight
;
244 grub_term_setcolorstate (data
->term
, highlight
245 ? GRUB_TERM_COLOR_HIGHLIGHT
246 : GRUB_TERM_COLOR_NORMAL
);
248 grub_term_gotoxy (data
->term
, (struct grub_term_coordinate
) {
249 data
->geo
.first_entry_x
, y
});
251 for (i
= 0; i
< len
; i
++)
252 if (unicode_title
[i
] == '\n' || unicode_title
[i
] == '\b'
253 || unicode_title
[i
] == '\r' || unicode_title
[i
] == '\e')
254 unicode_title
[i
] = ' ';
256 if (data
->geo
.num_entries
> 1)
257 grub_putcode (highlight
? '*' : ' ', data
->term
);
259 grub_print_ucs4_menu (unicode_title
,
262 data
->geo
.right_margin
,
264 GRUB_UNICODE_RIGHTARROW
, 0);
266 grub_term_setcolorstate (data
->term
, GRUB_TERM_COLOR_NORMAL
);
267 grub_term_gotoxy (data
->term
,
268 (struct grub_term_coordinate
) {
269 grub_term_cursor_x (&data
->geo
), y
});
271 grub_term_normal_color
= old_color_normal
;
272 grub_term_highlight_color
= old_color_highlight
;
274 grub_term_setcolorstate (data
->term
, GRUB_TERM_COLOR_NORMAL
);
275 grub_free (unicode_title
);
279 print_entries (grub_menu_t menu
, const struct menu_viewer_data
*data
)
284 grub_term_gotoxy (data
->term
,
285 (struct grub_term_coordinate
) {
286 data
->geo
.first_entry_x
+ data
->geo
.entry_width
287 + data
->geo
.border
+ 1,
288 data
->geo
.first_entry_y
});
290 if (data
->geo
.num_entries
!= 1)
293 grub_putcode (GRUB_UNICODE_UPARROW
, data
->term
);
295 grub_putcode (' ', data
->term
);
297 e
= grub_menu_get_entry (menu
, data
->first
);
299 for (i
= 0; i
< data
->geo
.num_entries
; i
++)
301 print_entry (data
->geo
.first_entry_y
+ i
, data
->offset
== i
,
307 grub_term_gotoxy (data
->term
,
308 (struct grub_term_coordinate
) { data
->geo
.first_entry_x
+ data
->geo
.entry_width
309 + data
->geo
.border
+ 1,
310 data
->geo
.first_entry_y
+ data
->geo
.num_entries
- 1 });
311 if (data
->geo
.num_entries
== 1)
313 if (data
->first
&& e
)
314 grub_putcode (GRUB_UNICODE_UPDOWNARROW
, data
->term
);
315 else if (data
->first
)
316 grub_putcode (GRUB_UNICODE_UPARROW
, data
->term
);
318 grub_putcode (GRUB_UNICODE_DOWNARROW
, data
->term
);
320 grub_putcode (' ', data
->term
);
325 grub_putcode (GRUB_UNICODE_DOWNARROW
, data
->term
);
327 grub_putcode (' ', data
->term
);
330 grub_term_gotoxy (data
->term
,
331 (struct grub_term_coordinate
) { grub_term_cursor_x (&data
->geo
),
332 data
->geo
.first_entry_y
+ data
->offset
});
335 /* Initialize the screen. If NESTED is non-zero, assume that this menu
336 is run from another menu or a command-line. If EDIT is non-zero, show
337 a message for the menu entry editor. */
339 grub_menu_init_page (int nested
, int edit
,
340 struct grub_term_screen_geometry
*geo
,
341 struct grub_term_output
*term
)
343 grub_uint8_t old_color_normal
, old_color_highlight
;
345 int bottom_message
= 1;
350 geo
->first_entry_x
= 1 /* margin */ + 1 /* border */;
351 geo
->entry_width
= grub_term_width (term
) - 5;
353 geo
->first_entry_y
= 2 /* two empty lines*/
354 + 1 /* GNU GRUB version text */ + 1 /* top border */;
356 geo
->timeout_lines
= 2;
358 /* 3 lines for timeout message and bottom margin. 2 lines for the border. */
359 geo
->num_entries
= grub_term_height (term
) - geo
->first_entry_y
360 - 1 /* bottom border */
361 - 1 /* empty line before info message*/
362 - geo
->timeout_lines
/* timeout */
363 - 1 /* empty final line */;
364 msg_num_lines
= print_message (nested
, edit
, term
, 1);
365 if (geo
->num_entries
- msg_num_lines
< 3
366 || geo
->entry_width
< 10)
368 geo
->num_entries
+= 4;
369 geo
->first_entry_y
-= 2;
371 geo
->first_entry_x
-= 1;
372 geo
->entry_width
+= 1;
374 if (geo
->num_entries
- msg_num_lines
< 3
375 || geo
->entry_width
< 10)
377 geo
->num_entries
+= 2;
378 geo
->first_entry_y
-= 1;
379 geo
->first_entry_x
-= 1;
380 geo
->entry_width
+= 2;
384 if (geo
->entry_width
<= 0)
385 geo
->entry_width
= 1;
387 if (geo
->num_entries
- msg_num_lines
< 3
388 && geo
->timeout_lines
== 2)
390 geo
->timeout_lines
= 1;
394 if (geo
->num_entries
- msg_num_lines
< 3)
396 geo
->num_entries
+= 1;
397 geo
->first_entry_y
-= 1;
401 if (geo
->num_entries
- msg_num_lines
>= 2)
402 geo
->num_entries
-= msg_num_lines
;
406 /* By default, use the same colors for the menu. */
407 old_color_normal
= grub_term_normal_color
;
408 old_color_highlight
= grub_term_highlight_color
;
409 grub_color_menu_normal
= grub_term_normal_color
;
410 grub_color_menu_highlight
= grub_term_highlight_color
;
412 /* Then give user a chance to replace them. */
413 grub_parse_color_name_pair (&grub_color_menu_normal
,
414 grub_env_get ("menu_color_normal"));
415 grub_parse_color_name_pair (&grub_color_menu_highlight
,
416 grub_env_get ("menu_color_highlight"));
419 grub_normal_init_page (term
, empty_lines
);
421 grub_term_cls (term
);
423 grub_term_normal_color
= grub_color_menu_normal
;
424 grub_term_highlight_color
= grub_color_menu_highlight
;
426 draw_border (term
, geo
);
427 grub_term_normal_color
= old_color_normal
;
428 grub_term_highlight_color
= old_color_highlight
;
429 geo
->timeout_y
= geo
->first_entry_y
+ geo
->num_entries
430 + geo
->border
+ empty_lines
;
433 grub_term_gotoxy (term
,
434 (struct grub_term_coordinate
) { GRUB_TERM_MARGIN
,
437 print_message (nested
, edit
, term
, 0);
438 geo
->timeout_y
+= msg_num_lines
;
440 geo
->right_margin
= grub_term_width (term
)
442 - geo
->entry_width
- 1;
446 menu_text_print_timeout (int timeout
, void *dataptr
)
448 struct menu_viewer_data
*data
= dataptr
;
449 char *msg_translated
= 0;
451 grub_term_gotoxy (data
->term
,
452 (struct grub_term_coordinate
) { 0, data
->geo
.timeout_y
});
454 if (data
->timeout_msg
== TIMEOUT_TERSE
455 || data
->timeout_msg
== TIMEOUT_TERSE_NO_MARGIN
)
456 msg_translated
= grub_xasprintf (_("%ds"), timeout
);
458 msg_translated
= grub_xasprintf (_("The highlighted entry will be executed automatically in %ds."), timeout
);
462 grub_errno
= GRUB_ERR_NONE
;
466 if (data
->timeout_msg
== TIMEOUT_UNKNOWN
)
468 data
->timeout_msg
= grub_print_message_indented_real (msg_translated
,
470 <= data
->geo
.timeout_lines
? TIMEOUT_NORMAL
: TIMEOUT_TERSE
;
471 if (data
->timeout_msg
== TIMEOUT_TERSE
)
473 grub_free (msg_translated
);
474 msg_translated
= grub_xasprintf (_("%ds"), timeout
);
475 if (grub_term_width (data
->term
) < 10)
476 data
->timeout_msg
= TIMEOUT_TERSE_NO_MARGIN
;
480 grub_print_message_indented (msg_translated
,
481 data
->timeout_msg
== TIMEOUT_TERSE_NO_MARGIN
? 0 : 3,
482 data
->timeout_msg
== TIMEOUT_TERSE_NO_MARGIN
? 0 : 1,
484 grub_free (msg_translated
);
486 grub_term_gotoxy (data
->term
,
487 (struct grub_term_coordinate
) {
488 grub_term_cursor_x (&data
->geo
),
489 data
->geo
.first_entry_y
+ data
->offset
});
490 grub_term_refresh (data
->term
);
494 menu_text_set_chosen_entry (int entry
, void *dataptr
)
496 struct menu_viewer_data
*data
= dataptr
;
497 int oldoffset
= data
->offset
;
498 int complete_redraw
= 0;
500 data
->offset
= entry
- data
->first
;
501 if (data
->offset
> data
->geo
.num_entries
- 1)
503 data
->first
= entry
- (data
->geo
.num_entries
- 1);
504 data
->offset
= data
->geo
.num_entries
- 1;
507 if (data
->offset
< 0)
514 print_entries (data
->menu
, data
);
517 print_entry (data
->geo
.first_entry_y
+ oldoffset
, 0,
518 grub_menu_get_entry (data
->menu
, data
->first
+ oldoffset
),
520 print_entry (data
->geo
.first_entry_y
+ data
->offset
, 1,
521 grub_menu_get_entry (data
->menu
, data
->first
+ data
->offset
),
524 grub_term_refresh (data
->term
);
528 menu_text_fini (void *dataptr
)
530 struct menu_viewer_data
*data
= dataptr
;
532 grub_term_setcursor (data
->term
, 1);
533 grub_term_cls (data
->term
);
538 menu_text_clear_timeout (void *dataptr
)
540 struct menu_viewer_data
*data
= dataptr
;
543 for (i
= 0; i
< data
->geo
.timeout_lines
;i
++)
545 grub_term_gotoxy (data
->term
, (struct grub_term_coordinate
) {
546 0, data
->geo
.timeout_y
+ i
});
547 grub_print_spaces (data
->term
, grub_term_width (data
->term
) - 1);
549 if (data
->geo
.num_entries
<= 5 && !data
->geo
.border
)
551 grub_term_gotoxy (data
->term
,
552 (struct grub_term_coordinate
) {
553 data
->geo
.first_entry_x
+ data
->geo
.entry_width
554 + data
->geo
.border
+ 1,
555 data
->geo
.first_entry_y
+ data
->geo
.num_entries
- 1
557 grub_putcode (' ', data
->term
);
559 data
->geo
.timeout_lines
= 0;
560 data
->geo
.num_entries
++;
561 print_entries (data
->menu
, data
);
563 grub_term_gotoxy (data
->term
,
564 (struct grub_term_coordinate
) {
565 grub_term_cursor_x (&data
->geo
),
566 data
->geo
.first_entry_y
+ data
->offset
});
567 grub_term_refresh (data
->term
);
571 grub_menu_try_text (struct grub_term_output
*term
,
572 int entry
, grub_menu_t menu
, int nested
)
574 struct menu_viewer_data
*data
;
575 struct grub_menu_viewer
*instance
;
577 instance
= grub_zalloc (sizeof (*instance
));
581 data
= grub_zalloc (sizeof (*data
));
584 grub_free (instance
);
589 instance
->data
= data
;
590 instance
->set_chosen_entry
= menu_text_set_chosen_entry
;
591 instance
->print_timeout
= menu_text_print_timeout
;
592 instance
->clear_timeout
= menu_text_clear_timeout
;
593 instance
->fini
= menu_text_fini
;
597 data
->offset
= entry
;
600 grub_term_setcursor (data
->term
, 0);
601 grub_menu_init_page (nested
, 0, &data
->geo
, data
->term
);
603 if (data
->offset
> data
->geo
.num_entries
- 1)
605 data
->first
= data
->offset
- (data
->geo
.num_entries
- 1);
606 data
->offset
= data
->geo
.num_entries
- 1;
609 print_entries (menu
, data
);
610 grub_term_refresh (data
->term
);
611 grub_menu_register_viewer (instance
);
613 return GRUB_ERR_NONE
;