]> glassweightruler.freedombox.rocks Git - Ventoy.git/blob - GRUB2/MOD_SRC/grub-2.04/grub-core/normal/menu_text.c
0f3ea8a695ff2c4a92acf90b753d9dbfa8959476
[Ventoy.git] / GRUB2 / MOD_SRC / grub-2.04 / grub-core / 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 #include <grub/charset.h>
30
31 static grub_uint8_t grub_color_menu_normal;
32 static grub_uint8_t grub_color_menu_highlight;
33
34 struct menu_viewer_data
35 {
36 int first, offset;
37 struct grub_term_screen_geometry geo;
38 enum {
39 TIMEOUT_UNKNOWN,
40 TIMEOUT_NORMAL,
41 TIMEOUT_TERSE,
42 TIMEOUT_TERSE_NO_MARGIN
43 } timeout_msg;
44 grub_menu_t menu;
45 struct grub_term_output *term;
46 };
47
48 static inline int
49 grub_term_cursor_x (const struct grub_term_screen_geometry *geo)
50 {
51 return (geo->first_entry_x + geo->entry_width);
52 }
53
54 grub_size_t
55 grub_getstringwidth (grub_uint32_t * str, const grub_uint32_t * last_position,
56 struct grub_term_output *term)
57 {
58 grub_ssize_t width = 0;
59
60 while (str < last_position)
61 {
62 struct grub_unicode_glyph glyph;
63 glyph.ncomb = 0;
64 str += grub_unicode_aglomerate_comb (str, last_position - str, &glyph);
65 width += grub_term_getcharwidth (term, &glyph);
66 grub_unicode_destroy_glyph (&glyph);
67 }
68 return width;
69 }
70
71 static int
72 grub_print_message_indented_real (const char *msg, int margin_left,
73 int margin_right,
74 struct grub_term_output *term, int dry_run)
75 {
76 grub_uint32_t *unicode_msg;
77 grub_uint32_t *last_position;
78 grub_size_t msg_len = grub_strlen (msg) + 2;
79 int ret = 0;
80
81 unicode_msg = grub_malloc (msg_len * sizeof (grub_uint32_t));
82
83 if (!unicode_msg)
84 return 0;
85
86 msg_len = grub_utf8_to_ucs4 (unicode_msg, msg_len,
87 (grub_uint8_t *) msg, -1, 0);
88
89 last_position = unicode_msg + msg_len;
90 *last_position = 0;
91
92 if (dry_run)
93 ret = grub_ucs4_count_lines (unicode_msg, last_position, margin_left,
94 margin_right, term);
95 else
96 grub_print_ucs4_menu (unicode_msg, last_position, margin_left,
97 margin_right, term, 0, -1, 0, 0);
98
99 grub_free (unicode_msg);
100
101 return ret;
102 }
103
104 void
105 grub_print_message_indented (const char *msg, int margin_left, int margin_right,
106 struct grub_term_output *term)
107 {
108 grub_print_message_indented_real (msg, margin_left, margin_right, term, 0);
109 }
110
111 static void
112 draw_border (struct grub_term_output *term, const struct grub_term_screen_geometry *geo)
113 {
114 int i;
115
116 grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL);
117
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);
124
125 for (i = 0; i < geo->num_entries; i++)
126 {
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);
134 }
135
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);
143
144 grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL);
145
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) });
150 }
151
152 static int
153 print_message (int nested, int edit, struct grub_term_output *term, int dry_run)
154 {
155 int ret = 0;
156 grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL);
157
158 if (edit)
159 {
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,
164 term, dry_run);
165 }
166 else
167 {
168 char *msg_translated;
169
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);
174 if (!msg_translated)
175 return 0;
176 ret += grub_print_message_indented_real (msg_translated, STANDARD_MARGIN,
177 STANDARD_MARGIN, term, dry_run);
178
179 grub_free (msg_translated);
180
181 if (nested)
182 {
183 #if 0
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);
189 #endif
190 }
191 else
192 {
193 char szLine[128];
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"));
197 } else {
198 grub_snprintf(szLine, sizeof(szLine), "%s", grub_env_get("VTOY_TEXT_MENU_VER"));
199 }
200
201 ret += grub_print_message_indented_real("\n", STANDARD_MARGIN, STANDARD_MARGIN, term, dry_run);
202
203 ret += grub_print_message_indented_real(szLine, STANDARD_MARGIN, STANDARD_MARGIN, term, dry_run);
204
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);
208 }
209 }
210 return ret;
211 }
212
213 static void
214 print_entry (int y, int highlight, grub_menu_entry_t entry,
215 const struct menu_viewer_data *data)
216 {
217 const char *title;
218 grub_size_t title_len;
219 grub_ssize_t len;
220 grub_uint32_t *unicode_title;
221 grub_ssize_t i;
222 grub_uint8_t old_color_normal, old_color_highlight;
223
224 title = entry ? entry->title : "";
225 title_len = grub_strlen (title);
226 unicode_title = grub_malloc (title_len * sizeof (*unicode_title));
227 if (! unicode_title)
228 /* XXX How to show this error? */
229 return;
230
231 len = grub_utf8_to_ucs4 (unicode_title, title_len,
232 (grub_uint8_t *) title, -1, 0);
233 if (len < 0)
234 {
235 /* It is an invalid sequence. */
236 grub_free (unicode_title);
237 return;
238 }
239
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);
247
248 grub_term_gotoxy (data->term, (struct grub_term_coordinate) {
249 data->geo.first_entry_x, y });
250
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] = ' ';
255
256 if (data->geo.num_entries > 1)
257 grub_putcode (highlight ? '*' : ' ', data->term);
258
259 grub_print_ucs4_menu (unicode_title,
260 unicode_title + len,
261 0,
262 data->geo.right_margin,
263 data->term, 0, 1,
264 GRUB_UNICODE_RIGHTARROW, 0);
265
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 });
270
271 grub_term_normal_color = old_color_normal;
272 grub_term_highlight_color = old_color_highlight;
273
274 grub_term_setcolorstate (data->term, GRUB_TERM_COLOR_NORMAL);
275 grub_free (unicode_title);
276 }
277
278 static void
279 print_entries (grub_menu_t menu, const struct menu_viewer_data *data)
280 {
281 grub_menu_entry_t e;
282 int i;
283
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 });
289
290 if (data->geo.num_entries != 1)
291 {
292 if (data->first)
293 grub_putcode (GRUB_UNICODE_UPARROW, data->term);
294 else
295 grub_putcode (' ', data->term);
296 }
297 e = grub_menu_get_entry (menu, data->first);
298
299 for (i = 0; i < data->geo.num_entries; i++)
300 {
301 print_entry (data->geo.first_entry_y + i, data->offset == i,
302 e, data);
303 if (e)
304 e = e->next;
305 }
306
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)
312 {
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);
317 else if (e)
318 grub_putcode (GRUB_UNICODE_DOWNARROW, data->term);
319 else
320 grub_putcode (' ', data->term);
321 }
322 else
323 {
324 if (e)
325 grub_putcode (GRUB_UNICODE_DOWNARROW, data->term);
326 else
327 grub_putcode (' ', data->term);
328 }
329
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 });
333 }
334
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. */
338 void
339 grub_menu_init_page (int nested, int edit,
340 struct grub_term_screen_geometry *geo,
341 struct grub_term_output *term)
342 {
343 grub_uint8_t old_color_normal, old_color_highlight;
344 int msg_num_lines;
345 int bottom_message = 1;
346 int empty_lines = 1;
347 int version_msg = 1;
348
349 geo->border = 1;
350 geo->first_entry_x = 1 /* margin */ + 1 /* border */;
351 geo->entry_width = grub_term_width (term) - 5;
352
353 geo->first_entry_y = 2 /* two empty lines*/
354 + 1 /* GNU GRUB version text */ + 1 /* top border */;
355
356 geo->timeout_lines = 2;
357
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)
367 {
368 geo->num_entries += 4;
369 geo->first_entry_y -= 2;
370 empty_lines = 0;
371 geo->first_entry_x -= 1;
372 geo->entry_width += 1;
373 }
374 if (geo->num_entries - msg_num_lines < 3
375 || geo->entry_width < 10)
376 {
377 geo->num_entries += 2;
378 geo->first_entry_y -= 1;
379 geo->first_entry_x -= 1;
380 geo->entry_width += 2;
381 geo->border = 0;
382 }
383
384 if (geo->entry_width <= 0)
385 geo->entry_width = 1;
386
387 if (geo->num_entries - msg_num_lines < 3
388 && geo->timeout_lines == 2)
389 {
390 geo->timeout_lines = 1;
391 geo->num_entries++;
392 }
393
394 if (geo->num_entries - msg_num_lines < 3)
395 {
396 geo->num_entries += 1;
397 geo->first_entry_y -= 1;
398 version_msg = 0;
399 }
400
401 if (geo->num_entries - msg_num_lines >= 2)
402 geo->num_entries -= msg_num_lines;
403 else
404 bottom_message = 0;
405
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;
411
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"));
417
418 if (version_msg)
419 grub_normal_init_page (term, empty_lines);
420 else
421 grub_term_cls (term);
422
423 grub_term_normal_color = grub_color_menu_normal;
424 grub_term_highlight_color = grub_color_menu_highlight;
425 if (geo->border)
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;
431 if (bottom_message)
432 {
433 grub_term_gotoxy (term,
434 (struct grub_term_coordinate) { GRUB_TERM_MARGIN,
435 geo->timeout_y });
436
437 print_message (nested, edit, term, 0);
438 geo->timeout_y += msg_num_lines;
439 }
440 geo->right_margin = grub_term_width (term)
441 - geo->first_entry_x
442 - geo->entry_width - 1;
443 }
444
445 static void
446 menu_text_print_timeout (int timeout, void *dataptr)
447 {
448 struct menu_viewer_data *data = dataptr;
449 char *msg_translated = 0;
450
451 grub_term_gotoxy (data->term,
452 (struct grub_term_coordinate) { 0, data->geo.timeout_y });
453
454 if (data->timeout_msg == TIMEOUT_TERSE
455 || data->timeout_msg == TIMEOUT_TERSE_NO_MARGIN)
456 msg_translated = grub_xasprintf (_("%ds"), timeout);
457 else
458 msg_translated = grub_xasprintf (_("The highlighted entry will be executed automatically in %ds."), timeout);
459 if (!msg_translated)
460 {
461 grub_print_error ();
462 grub_errno = GRUB_ERR_NONE;
463 return;
464 }
465
466 if (data->timeout_msg == TIMEOUT_UNKNOWN)
467 {
468 data->timeout_msg = grub_print_message_indented_real (msg_translated,
469 3, 1, data->term, 1)
470 <= data->geo.timeout_lines ? TIMEOUT_NORMAL : TIMEOUT_TERSE;
471 if (data->timeout_msg == TIMEOUT_TERSE)
472 {
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;
477 }
478 }
479
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,
483 data->term);
484 grub_free (msg_translated);
485
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);
491 }
492
493 static void
494 menu_text_set_chosen_entry (int entry, void *dataptr)
495 {
496 struct menu_viewer_data *data = dataptr;
497 int oldoffset = data->offset;
498 int complete_redraw = 0;
499
500 data->offset = entry - data->first;
501 if (data->offset > data->geo.num_entries - 1)
502 {
503 data->first = entry - (data->geo.num_entries - 1);
504 data->offset = data->geo.num_entries - 1;
505 complete_redraw = 1;
506 }
507 if (data->offset < 0)
508 {
509 data->offset = 0;
510 data->first = entry;
511 complete_redraw = 1;
512 }
513 if (complete_redraw)
514 print_entries (data->menu, data);
515 else
516 {
517 print_entry (data->geo.first_entry_y + oldoffset, 0,
518 grub_menu_get_entry (data->menu, data->first + oldoffset),
519 data);
520 print_entry (data->geo.first_entry_y + data->offset, 1,
521 grub_menu_get_entry (data->menu, data->first + data->offset),
522 data);
523 }
524 grub_term_refresh (data->term);
525 }
526
527 static void
528 menu_text_fini (void *dataptr)
529 {
530 struct menu_viewer_data *data = dataptr;
531
532 grub_term_setcursor (data->term, 1);
533 grub_term_cls (data->term);
534 grub_free (data);
535 }
536
537 static void
538 menu_text_clear_timeout (void *dataptr)
539 {
540 struct menu_viewer_data *data = dataptr;
541 int i;
542
543 for (i = 0; i < data->geo.timeout_lines;i++)
544 {
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);
548 }
549 if (data->geo.num_entries <= 5 && !data->geo.border)
550 {
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
556 });
557 grub_putcode (' ', data->term);
558
559 data->geo.timeout_lines = 0;
560 data->geo.num_entries++;
561 print_entries (data->menu, data);
562 }
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);
568 }
569
570 grub_err_t
571 grub_menu_try_text (struct grub_term_output *term,
572 int entry, grub_menu_t menu, int nested)
573 {
574 struct menu_viewer_data *data;
575 struct grub_menu_viewer *instance;
576
577 instance = grub_zalloc (sizeof (*instance));
578 if (!instance)
579 return grub_errno;
580
581 data = grub_zalloc (sizeof (*data));
582 if (!data)
583 {
584 grub_free (instance);
585 return grub_errno;
586 }
587
588 data->term = term;
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;
594
595 data->menu = menu;
596
597 data->offset = entry;
598 data->first = 0;
599
600 grub_term_setcursor (data->term, 0);
601 grub_menu_init_page (nested, 0, &data->geo, data->term);
602
603 if (data->offset > data->geo.num_entries - 1)
604 {
605 data->first = data->offset - (data->geo.num_entries - 1);
606 data->offset = data->geo.num_entries - 1;
607 }
608
609 print_entries (menu, data);
610 grub_term_refresh (data->term);
611 grub_menu_register_viewer (instance);
612
613 return GRUB_ERR_NONE;
614 }