]> glassweightruler.freedombox.rocks Git - Ventoy.git/blob - GRUB2/MOD_SRC/grub-2.04/grub-core/normal/menu_text.c
cd96ceb181f259116c40900b58f5b205743b91cb
[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 ret += grub_print_message_indented_real("\n", STANDARD_MARGIN, STANDARD_MARGIN, term, dry_run);
194
195 ret += grub_print_message_indented_real(grub_env_get("VTOY_TEXT_MENU_VER"),
196 STANDARD_MARGIN, STANDARD_MARGIN, term, dry_run);
197
198 ret += grub_print_message_indented_real("\n", STANDARD_MARGIN, STANDARD_MARGIN, term, dry_run);
199 ret += grub_print_message_indented_real(grub_env_get("VTOY_HOTKEY_TIP"),
200 3, 6, term, dry_run);
201 }
202 }
203 return ret;
204 }
205
206 static void
207 print_entry (int y, int highlight, grub_menu_entry_t entry,
208 const struct menu_viewer_data *data)
209 {
210 const char *title;
211 grub_size_t title_len;
212 grub_ssize_t len;
213 grub_uint32_t *unicode_title;
214 grub_ssize_t i;
215 grub_uint8_t old_color_normal, old_color_highlight;
216
217 title = entry ? entry->title : "";
218 title_len = grub_strlen (title);
219 unicode_title = grub_malloc (title_len * sizeof (*unicode_title));
220 if (! unicode_title)
221 /* XXX How to show this error? */
222 return;
223
224 len = grub_utf8_to_ucs4 (unicode_title, title_len,
225 (grub_uint8_t *) title, -1, 0);
226 if (len < 0)
227 {
228 /* It is an invalid sequence. */
229 grub_free (unicode_title);
230 return;
231 }
232
233 old_color_normal = grub_term_normal_color;
234 old_color_highlight = grub_term_highlight_color;
235 grub_term_normal_color = grub_color_menu_normal;
236 grub_term_highlight_color = grub_color_menu_highlight;
237 grub_term_setcolorstate (data->term, highlight
238 ? GRUB_TERM_COLOR_HIGHLIGHT
239 : GRUB_TERM_COLOR_NORMAL);
240
241 grub_term_gotoxy (data->term, (struct grub_term_coordinate) {
242 data->geo.first_entry_x, y });
243
244 for (i = 0; i < len; i++)
245 if (unicode_title[i] == '\n' || unicode_title[i] == '\b'
246 || unicode_title[i] == '\r' || unicode_title[i] == '\e')
247 unicode_title[i] = ' ';
248
249 if (data->geo.num_entries > 1)
250 grub_putcode (highlight ? '*' : ' ', data->term);
251
252 grub_print_ucs4_menu (unicode_title,
253 unicode_title + len,
254 0,
255 data->geo.right_margin,
256 data->term, 0, 1,
257 GRUB_UNICODE_RIGHTARROW, 0);
258
259 grub_term_setcolorstate (data->term, GRUB_TERM_COLOR_NORMAL);
260 grub_term_gotoxy (data->term,
261 (struct grub_term_coordinate) {
262 grub_term_cursor_x (&data->geo), y });
263
264 grub_term_normal_color = old_color_normal;
265 grub_term_highlight_color = old_color_highlight;
266
267 grub_term_setcolorstate (data->term, GRUB_TERM_COLOR_NORMAL);
268 grub_free (unicode_title);
269 }
270
271 static void
272 print_entries (grub_menu_t menu, const struct menu_viewer_data *data)
273 {
274 grub_menu_entry_t e;
275 int i;
276
277 grub_term_gotoxy (data->term,
278 (struct grub_term_coordinate) {
279 data->geo.first_entry_x + data->geo.entry_width
280 + data->geo.border + 1,
281 data->geo.first_entry_y });
282
283 if (data->geo.num_entries != 1)
284 {
285 if (data->first)
286 grub_putcode (GRUB_UNICODE_UPARROW, data->term);
287 else
288 grub_putcode (' ', data->term);
289 }
290 e = grub_menu_get_entry (menu, data->first);
291
292 for (i = 0; i < data->geo.num_entries; i++)
293 {
294 print_entry (data->geo.first_entry_y + i, data->offset == i,
295 e, data);
296 if (e)
297 e = e->next;
298 }
299
300 grub_term_gotoxy (data->term,
301 (struct grub_term_coordinate) { data->geo.first_entry_x + data->geo.entry_width
302 + data->geo.border + 1,
303 data->geo.first_entry_y + data->geo.num_entries - 1 });
304 if (data->geo.num_entries == 1)
305 {
306 if (data->first && e)
307 grub_putcode (GRUB_UNICODE_UPDOWNARROW, data->term);
308 else if (data->first)
309 grub_putcode (GRUB_UNICODE_UPARROW, data->term);
310 else if (e)
311 grub_putcode (GRUB_UNICODE_DOWNARROW, data->term);
312 else
313 grub_putcode (' ', data->term);
314 }
315 else
316 {
317 if (e)
318 grub_putcode (GRUB_UNICODE_DOWNARROW, data->term);
319 else
320 grub_putcode (' ', data->term);
321 }
322
323 grub_term_gotoxy (data->term,
324 (struct grub_term_coordinate) { grub_term_cursor_x (&data->geo),
325 data->geo.first_entry_y + data->offset });
326 }
327
328 /* Initialize the screen. If NESTED is non-zero, assume that this menu
329 is run from another menu or a command-line. If EDIT is non-zero, show
330 a message for the menu entry editor. */
331 void
332 grub_menu_init_page (int nested, int edit,
333 struct grub_term_screen_geometry *geo,
334 struct grub_term_output *term)
335 {
336 grub_uint8_t old_color_normal, old_color_highlight;
337 int msg_num_lines;
338 int bottom_message = 1;
339 int empty_lines = 1;
340 int version_msg = 1;
341
342 geo->border = 1;
343 geo->first_entry_x = 1 /* margin */ + 1 /* border */;
344 geo->entry_width = grub_term_width (term) - 5;
345
346 geo->first_entry_y = 2 /* two empty lines*/
347 + 1 /* GNU GRUB version text */ + 1 /* top border */;
348
349 geo->timeout_lines = 2;
350
351 /* 3 lines for timeout message and bottom margin. 2 lines for the border. */
352 geo->num_entries = grub_term_height (term) - geo->first_entry_y
353 - 1 /* bottom border */
354 - 1 /* empty line before info message*/
355 - geo->timeout_lines /* timeout */
356 - 1 /* empty final line */;
357 msg_num_lines = print_message (nested, edit, term, 1);
358 if (geo->num_entries - msg_num_lines < 3
359 || geo->entry_width < 10)
360 {
361 geo->num_entries += 4;
362 geo->first_entry_y -= 2;
363 empty_lines = 0;
364 geo->first_entry_x -= 1;
365 geo->entry_width += 1;
366 }
367 if (geo->num_entries - msg_num_lines < 3
368 || geo->entry_width < 10)
369 {
370 geo->num_entries += 2;
371 geo->first_entry_y -= 1;
372 geo->first_entry_x -= 1;
373 geo->entry_width += 2;
374 geo->border = 0;
375 }
376
377 if (geo->entry_width <= 0)
378 geo->entry_width = 1;
379
380 if (geo->num_entries - msg_num_lines < 3
381 && geo->timeout_lines == 2)
382 {
383 geo->timeout_lines = 1;
384 geo->num_entries++;
385 }
386
387 if (geo->num_entries - msg_num_lines < 3)
388 {
389 geo->num_entries += 1;
390 geo->first_entry_y -= 1;
391 version_msg = 0;
392 }
393
394 if (geo->num_entries - msg_num_lines >= 2)
395 geo->num_entries -= msg_num_lines;
396 else
397 bottom_message = 0;
398
399 /* By default, use the same colors for the menu. */
400 old_color_normal = grub_term_normal_color;
401 old_color_highlight = grub_term_highlight_color;
402 grub_color_menu_normal = grub_term_normal_color;
403 grub_color_menu_highlight = grub_term_highlight_color;
404
405 /* Then give user a chance to replace them. */
406 grub_parse_color_name_pair (&grub_color_menu_normal,
407 grub_env_get ("menu_color_normal"));
408 grub_parse_color_name_pair (&grub_color_menu_highlight,
409 grub_env_get ("menu_color_highlight"));
410
411 if (version_msg)
412 grub_normal_init_page (term, empty_lines);
413 else
414 grub_term_cls (term);
415
416 grub_term_normal_color = grub_color_menu_normal;
417 grub_term_highlight_color = grub_color_menu_highlight;
418 if (geo->border)
419 draw_border (term, geo);
420 grub_term_normal_color = old_color_normal;
421 grub_term_highlight_color = old_color_highlight;
422 geo->timeout_y = geo->first_entry_y + geo->num_entries
423 + geo->border + empty_lines;
424 if (bottom_message)
425 {
426 grub_term_gotoxy (term,
427 (struct grub_term_coordinate) { GRUB_TERM_MARGIN,
428 geo->timeout_y });
429
430 print_message (nested, edit, term, 0);
431 geo->timeout_y += msg_num_lines;
432 }
433 geo->right_margin = grub_term_width (term)
434 - geo->first_entry_x
435 - geo->entry_width - 1;
436 }
437
438 static void
439 menu_text_print_timeout (int timeout, void *dataptr)
440 {
441 struct menu_viewer_data *data = dataptr;
442 char *msg_translated = 0;
443
444 grub_term_gotoxy (data->term,
445 (struct grub_term_coordinate) { 0, data->geo.timeout_y });
446
447 if (data->timeout_msg == TIMEOUT_TERSE
448 || data->timeout_msg == TIMEOUT_TERSE_NO_MARGIN)
449 msg_translated = grub_xasprintf (_("%ds"), timeout);
450 else
451 msg_translated = grub_xasprintf (_("The highlighted entry will be executed automatically in %ds."), timeout);
452 if (!msg_translated)
453 {
454 grub_print_error ();
455 grub_errno = GRUB_ERR_NONE;
456 return;
457 }
458
459 if (data->timeout_msg == TIMEOUT_UNKNOWN)
460 {
461 data->timeout_msg = grub_print_message_indented_real (msg_translated,
462 3, 1, data->term, 1)
463 <= data->geo.timeout_lines ? TIMEOUT_NORMAL : TIMEOUT_TERSE;
464 if (data->timeout_msg == TIMEOUT_TERSE)
465 {
466 grub_free (msg_translated);
467 msg_translated = grub_xasprintf (_("%ds"), timeout);
468 if (grub_term_width (data->term) < 10)
469 data->timeout_msg = TIMEOUT_TERSE_NO_MARGIN;
470 }
471 }
472
473 grub_print_message_indented (msg_translated,
474 data->timeout_msg == TIMEOUT_TERSE_NO_MARGIN ? 0 : 3,
475 data->timeout_msg == TIMEOUT_TERSE_NO_MARGIN ? 0 : 1,
476 data->term);
477 grub_free (msg_translated);
478
479 grub_term_gotoxy (data->term,
480 (struct grub_term_coordinate) {
481 grub_term_cursor_x (&data->geo),
482 data->geo.first_entry_y + data->offset });
483 grub_term_refresh (data->term);
484 }
485
486 static void
487 menu_text_set_chosen_entry (int entry, void *dataptr)
488 {
489 struct menu_viewer_data *data = dataptr;
490 int oldoffset = data->offset;
491 int complete_redraw = 0;
492
493 data->offset = entry - data->first;
494 if (data->offset > data->geo.num_entries - 1)
495 {
496 data->first = entry - (data->geo.num_entries - 1);
497 data->offset = data->geo.num_entries - 1;
498 complete_redraw = 1;
499 }
500 if (data->offset < 0)
501 {
502 data->offset = 0;
503 data->first = entry;
504 complete_redraw = 1;
505 }
506 if (complete_redraw)
507 print_entries (data->menu, data);
508 else
509 {
510 print_entry (data->geo.first_entry_y + oldoffset, 0,
511 grub_menu_get_entry (data->menu, data->first + oldoffset),
512 data);
513 print_entry (data->geo.first_entry_y + data->offset, 1,
514 grub_menu_get_entry (data->menu, data->first + data->offset),
515 data);
516 }
517 grub_term_refresh (data->term);
518 }
519
520 static void
521 menu_text_fini (void *dataptr)
522 {
523 struct menu_viewer_data *data = dataptr;
524
525 grub_term_setcursor (data->term, 1);
526 grub_term_cls (data->term);
527 grub_free (data);
528 }
529
530 static void
531 menu_text_clear_timeout (void *dataptr)
532 {
533 struct menu_viewer_data *data = dataptr;
534 int i;
535
536 for (i = 0; i < data->geo.timeout_lines;i++)
537 {
538 grub_term_gotoxy (data->term, (struct grub_term_coordinate) {
539 0, data->geo.timeout_y + i });
540 grub_print_spaces (data->term, grub_term_width (data->term) - 1);
541 }
542 if (data->geo.num_entries <= 5 && !data->geo.border)
543 {
544 grub_term_gotoxy (data->term,
545 (struct grub_term_coordinate) {
546 data->geo.first_entry_x + data->geo.entry_width
547 + data->geo.border + 1,
548 data->geo.first_entry_y + data->geo.num_entries - 1
549 });
550 grub_putcode (' ', data->term);
551
552 data->geo.timeout_lines = 0;
553 data->geo.num_entries++;
554 print_entries (data->menu, data);
555 }
556 grub_term_gotoxy (data->term,
557 (struct grub_term_coordinate) {
558 grub_term_cursor_x (&data->geo),
559 data->geo.first_entry_y + data->offset });
560 grub_term_refresh (data->term);
561 }
562
563 grub_err_t
564 grub_menu_try_text (struct grub_term_output *term,
565 int entry, grub_menu_t menu, int nested)
566 {
567 struct menu_viewer_data *data;
568 struct grub_menu_viewer *instance;
569
570 instance = grub_zalloc (sizeof (*instance));
571 if (!instance)
572 return grub_errno;
573
574 data = grub_zalloc (sizeof (*data));
575 if (!data)
576 {
577 grub_free (instance);
578 return grub_errno;
579 }
580
581 data->term = term;
582 instance->data = data;
583 instance->set_chosen_entry = menu_text_set_chosen_entry;
584 instance->print_timeout = menu_text_print_timeout;
585 instance->clear_timeout = menu_text_clear_timeout;
586 instance->fini = menu_text_fini;
587
588 data->menu = menu;
589
590 data->offset = entry;
591 data->first = 0;
592
593 grub_term_setcursor (data->term, 0);
594 grub_menu_init_page (nested, 0, &data->geo, data->term);
595
596 if (data->offset > data->geo.num_entries - 1)
597 {
598 data->first = data->offset - (data->geo.num_entries - 1);
599 data->offset = data->geo.num_entries - 1;
600 }
601
602 print_entries (menu, data);
603 grub_term_refresh (data->term);
604 grub_menu_register_viewer (instance);
605
606 return GRUB_ERR_NONE;
607 }