]> glassweightruler.freedombox.rocks Git - Ventoy.git/blob - GRUB2/MOD_SRC/grub-2.04/grub-core/normal/menu_text.c
24aa094d7c1cbfd9b8d4046374ebf6fb5cf9b163
[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 int *menu_title_offset;
46 struct grub_term_output *term;
47 };
48
49 static inline int
50 grub_term_cursor_x (const struct grub_term_screen_geometry *geo)
51 {
52 return (geo->first_entry_x + geo->entry_width);
53 }
54
55 grub_size_t
56 grub_getstringwidth (grub_uint32_t * str, const grub_uint32_t * last_position,
57 struct grub_term_output *term)
58 {
59 grub_ssize_t width = 0;
60
61 while (str < last_position)
62 {
63 struct grub_unicode_glyph glyph;
64 glyph.ncomb = 0;
65 str += grub_unicode_aglomerate_comb (str, last_position - str, &glyph);
66 width += grub_term_getcharwidth (term, &glyph);
67 grub_unicode_destroy_glyph (&glyph);
68 }
69 return width;
70 }
71
72 static int
73 grub_print_message_indented_real (const char *msg, int margin_left,
74 int margin_right,
75 struct grub_term_output *term, int dry_run)
76 {
77 grub_uint32_t *unicode_msg;
78 grub_uint32_t *last_position;
79 grub_size_t msg_len = grub_strlen (msg) + 2;
80 int ret = 0;
81
82 unicode_msg = grub_malloc (msg_len * sizeof (grub_uint32_t));
83
84 if (!unicode_msg)
85 return 0;
86
87 msg_len = grub_utf8_to_ucs4 (unicode_msg, msg_len,
88 (grub_uint8_t *) msg, -1, 0);
89
90 last_position = unicode_msg + msg_len;
91 *last_position = 0;
92
93 if (dry_run)
94 ret = grub_ucs4_count_lines (unicode_msg, last_position, margin_left,
95 margin_right, term);
96 else
97 grub_print_ucs4_menu (unicode_msg, last_position, margin_left,
98 margin_right, term, 0, -1, 0, 0);
99
100 grub_free (unicode_msg);
101
102 return ret;
103 }
104
105 void
106 grub_print_message_indented (const char *msg, int margin_left, int margin_right,
107 struct grub_term_output *term)
108 {
109 grub_print_message_indented_real (msg, margin_left, margin_right, term, 0);
110 }
111
112 static void
113 draw_border (struct grub_term_output *term, const struct grub_term_screen_geometry *geo)
114 {
115 int i;
116
117 grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL);
118
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);
125
126 for (i = 0; i < geo->num_entries; i++)
127 {
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);
135 }
136
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);
144
145 grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL);
146
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) });
151 }
152
153 static int
154 print_message (int nested, int edit, struct grub_term_output *term, int dry_run)
155 {
156 int ret = 0;
157 grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL);
158
159 if (edit)
160 {
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,
165 term, dry_run);
166 }
167 else
168 {
169 char *msg_translated;
170
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);
175 if (!msg_translated)
176 return 0;
177 ret += grub_print_message_indented_real (msg_translated, STANDARD_MARGIN,
178 STANDARD_MARGIN, term, dry_run);
179
180 grub_free (msg_translated);
181
182 if (nested)
183 {
184 #if 0
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);
190 #endif
191 }
192 else
193 {
194 char szLine[128];
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"));
198 } else {
199 grub_snprintf(szLine, sizeof(szLine), "%s", grub_env_get("VTOY_TEXT_MENU_VER"));
200 }
201
202 ret += grub_print_message_indented_real("\n", STANDARD_MARGIN, STANDARD_MARGIN, term, dry_run);
203
204 ret += grub_print_message_indented_real(szLine, STANDARD_MARGIN, STANDARD_MARGIN, term, dry_run);
205
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);
209 }
210 }
211 return ret;
212 }
213
214 static void
215 print_entry (int y, int highlight, grub_menu_entry_t entry,
216 const struct menu_viewer_data *data)
217 {
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 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);
248
249 grub_term_gotoxy (data->term, (struct grub_term_coordinate) {
250 data->geo.first_entry_x, y });
251
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] = ' ';
256
257 if (data->geo.num_entries > 1)
258 grub_putcode (highlight ? '*' : ' ', data->term);
259
260 grub_print_ucs4_menu (unicode_title,
261 unicode_title + len,
262 0,
263 data->geo.right_margin,
264 data->term, 0, 1,
265 GRUB_UNICODE_RIGHTARROW, 0);
266
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 });
271
272 grub_term_normal_color = old_color_normal;
273 grub_term_highlight_color = old_color_highlight;
274
275 grub_term_setcolorstate (data->term, GRUB_TERM_COLOR_NORMAL);
276 grub_free (unicode_title);
277 }
278
279 static void
280 print_entries (grub_menu_t menu, const struct menu_viewer_data *data)
281 {
282 grub_menu_entry_t e;
283 int i;
284
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 });
290
291 if (data->geo.num_entries != 1)
292 {
293 if (data->first)
294 grub_putcode (GRUB_UNICODE_UPARROW, data->term);
295 else
296 grub_putcode (' ', data->term);
297 }
298 e = grub_menu_get_entry (menu, data->first);
299
300 for (i = 0; i < data->geo.num_entries; i++)
301 {
302 print_entry (data->geo.first_entry_y + i, data->offset == i,
303 e, data);
304 if (e)
305 e = e->next;
306 }
307
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)
313 {
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);
318 else if (e)
319 grub_putcode (GRUB_UNICODE_DOWNARROW, data->term);
320 else
321 grub_putcode (' ', data->term);
322 }
323 else
324 {
325 if (e)
326 grub_putcode (GRUB_UNICODE_DOWNARROW, data->term);
327 else
328 grub_putcode (' ', data->term);
329 }
330
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 });
334 }
335
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. */
339 void
340 grub_menu_init_page (int nested, int edit,
341 struct grub_term_screen_geometry *geo,
342 struct grub_term_output *term)
343 {
344 grub_uint8_t old_color_normal, old_color_highlight;
345 int msg_num_lines;
346 int bottom_message = 1;
347 int empty_lines = 1;
348 int version_msg = 1;
349
350 geo->border = 1;
351 geo->first_entry_x = 1 /* margin */ + 1 /* border */;
352 geo->entry_width = grub_term_width (term) - 5;
353
354 geo->first_entry_y = 2 /* two empty lines*/
355 + 1 /* GNU GRUB version text */ + 1 /* top border */;
356
357 geo->timeout_lines = 2;
358
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)
368 {
369 geo->num_entries += 4;
370 geo->first_entry_y -= 2;
371 empty_lines = 0;
372 geo->first_entry_x -= 1;
373 geo->entry_width += 1;
374 }
375 if (geo->num_entries - msg_num_lines < 3
376 || geo->entry_width < 10)
377 {
378 geo->num_entries += 2;
379 geo->first_entry_y -= 1;
380 geo->first_entry_x -= 1;
381 geo->entry_width += 2;
382 geo->border = 0;
383 }
384
385 if (geo->entry_width <= 0)
386 geo->entry_width = 1;
387
388 if (geo->num_entries - msg_num_lines < 3
389 && geo->timeout_lines == 2)
390 {
391 geo->timeout_lines = 1;
392 geo->num_entries++;
393 }
394
395 if (geo->num_entries - msg_num_lines < 3)
396 {
397 geo->num_entries += 1;
398 geo->first_entry_y -= 1;
399 version_msg = 0;
400 }
401
402 if (geo->num_entries - msg_num_lines >= 2)
403 geo->num_entries -= msg_num_lines;
404 else
405 bottom_message = 0;
406
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;
412
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"));
418
419 if (version_msg)
420 grub_normal_init_page (term, empty_lines);
421 else
422 grub_term_cls (term);
423
424 grub_term_normal_color = grub_color_menu_normal;
425 grub_term_highlight_color = grub_color_menu_highlight;
426 if (geo->border)
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;
432 if (bottom_message)
433 {
434 grub_term_gotoxy (term,
435 (struct grub_term_coordinate) { GRUB_TERM_MARGIN,
436 geo->timeout_y });
437
438 print_message (nested, edit, term, 0);
439 geo->timeout_y += msg_num_lines;
440 }
441 geo->right_margin = grub_term_width (term)
442 - geo->first_entry_x
443 - geo->entry_width - 1;
444 }
445
446 static void
447 menu_text_print_timeout (int timeout, void *dataptr)
448 {
449 struct menu_viewer_data *data = dataptr;
450 char *msg_translated = 0;
451
452 grub_term_gotoxy (data->term,
453 (struct grub_term_coordinate) { 0, data->geo.timeout_y });
454
455 if (data->timeout_msg == TIMEOUT_TERSE
456 || data->timeout_msg == TIMEOUT_TERSE_NO_MARGIN)
457 msg_translated = grub_xasprintf (_("%ds"), timeout);
458 else
459 msg_translated = grub_xasprintf (_("The highlighted entry will be executed automatically in %ds."), timeout);
460 if (!msg_translated)
461 {
462 grub_print_error ();
463 grub_errno = GRUB_ERR_NONE;
464 return;
465 }
466
467 if (data->timeout_msg == TIMEOUT_UNKNOWN)
468 {
469 data->timeout_msg = grub_print_message_indented_real (msg_translated,
470 3, 1, data->term, 1)
471 <= data->geo.timeout_lines ? TIMEOUT_NORMAL : TIMEOUT_TERSE;
472 if (data->timeout_msg == TIMEOUT_TERSE)
473 {
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;
478 }
479 }
480
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,
484 data->term);
485 grub_free (msg_translated);
486
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);
492 }
493
494 static void
495 menu_text_set_chosen_entry (int entry, void *dataptr)
496 {
497 struct menu_viewer_data *data = dataptr;
498 int oldoffset = data->offset;
499 int complete_redraw = 0;
500
501 data->offset = entry - data->first;
502 if (data->offset > data->geo.num_entries - 1)
503 {
504 data->first = entry - (data->geo.num_entries - 1);
505 data->offset = data->geo.num_entries - 1;
506 complete_redraw = 1;
507 }
508 if (data->offset < 0)
509 {
510 data->offset = 0;
511 data->first = entry;
512 complete_redraw = 1;
513 }
514 if (complete_redraw)
515 print_entries (data->menu, data);
516 else
517 {
518 print_entry (data->geo.first_entry_y + oldoffset, 0,
519 grub_menu_get_entry (data->menu, data->first + oldoffset),
520 data);
521 print_entry (data->geo.first_entry_y + data->offset, 1,
522 grub_menu_get_entry (data->menu, data->first + data->offset),
523 data);
524 }
525 grub_term_refresh (data->term);
526 }
527
528 static void
529 menu_text_scroll_chosen_entry (void *dataptr, int diren)
530 {
531 struct menu_viewer_data *data = dataptr;
532 const char *orig_title, *scrolled_title;
533 int off;
534 int selected;
535 grub_menu_entry_t entry;
536
537 if (!data->menu->size)
538 return;
539
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;
544 if (off < 0
545 || off > grub_utf8_get_num_code (orig_title, grub_strlen(orig_title)))
546 return;
547
548 scrolled_title =
549 grub_utf8_offset_code (orig_title, grub_strlen (orig_title), off);
550 if (scrolled_title)
551 entry->title = scrolled_title;
552 print_entry (data->geo.first_entry_y + data->offset, 1, entry, data);
553
554 entry->title = orig_title;
555 data->menu_title_offset[selected] = off;
556 grub_term_refresh (data->term);
557 }
558
559 static void
560 menu_text_fini (void *dataptr)
561 {
562 struct menu_viewer_data *data = dataptr;
563
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);
568 grub_free (data);
569 }
570
571 static void
572 menu_text_clear_timeout (void *dataptr)
573 {
574 struct menu_viewer_data *data = dataptr;
575 int i;
576
577 for (i = 0; i < data->geo.timeout_lines;i++)
578 {
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);
582 }
583 if (data->geo.num_entries <= 5 && !data->geo.border)
584 {
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
590 });
591 grub_putcode (' ', data->term);
592
593 data->geo.timeout_lines = 0;
594 data->geo.num_entries++;
595 print_entries (data->menu, data);
596 }
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);
602 }
603
604 grub_err_t
605 grub_menu_try_text (struct grub_term_output *term,
606 int entry, grub_menu_t menu, int nested)
607 {
608 struct menu_viewer_data *data;
609 struct grub_menu_viewer *instance;
610
611 instance = grub_zalloc (sizeof (*instance));
612 if (!instance)
613 return grub_errno;
614
615 data = grub_zalloc (sizeof (*data));
616 if (!data)
617 {
618 grub_free (instance);
619 return grub_errno;
620 }
621
622 if (menu->size)
623 data->menu_title_offset = grub_zalloc (sizeof (*data->menu_title_offset) * menu->size);
624
625 data->term = term;
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;
633
634 data->menu = menu;
635
636 data->offset = entry;
637 data->first = 0;
638
639 grub_term_setcursor (data->term, 0);
640 grub_menu_init_page (nested, 0, &data->geo, data->term);
641
642 if (data->offset > data->geo.num_entries - 1)
643 {
644 data->first = data->offset - (data->geo.num_entries - 1);
645 data->offset = data->geo.num_entries - 1;
646 }
647
648 print_entries (menu, data);
649 grub_term_refresh (data->term);
650 grub_menu_register_viewer (instance);
651
652 return GRUB_ERR_NONE;
653 }