]> git.proxmox.com Git - grub2.git/blob - grub-core/normal/menu_text.c
Merge mainline into arm
[grub2.git] / 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_ssize_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, geo->first_entry_x - 1, geo->first_entry_y - 1);
119 grub_putcode (GRUB_UNICODE_CORNER_UL, term);
120 for (i = 0; i < geo->entry_width + 1; i++)
121 grub_putcode (GRUB_UNICODE_HLINE, term);
122 grub_putcode (GRUB_UNICODE_CORNER_UR, term);
123
124 for (i = 0; i < geo->num_entries; i++)
125 {
126 grub_term_gotoxy (term, geo->first_entry_x - 1, geo->first_entry_y + i);
127 grub_putcode (GRUB_UNICODE_VLINE, term);
128 grub_term_gotoxy (term, geo->first_entry_x + geo->entry_width + 1,
129 geo->first_entry_y + i);
130 grub_putcode (GRUB_UNICODE_VLINE, term);
131 }
132
133 grub_term_gotoxy (term, geo->first_entry_x - 1,
134 geo->first_entry_y - 1 + geo->num_entries + 1);
135 grub_putcode (GRUB_UNICODE_CORNER_LL, term);
136 for (i = 0; i < geo->entry_width + 1; i++)
137 grub_putcode (GRUB_UNICODE_HLINE, term);
138 grub_putcode (GRUB_UNICODE_CORNER_LR, term);
139
140 grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL);
141
142 grub_term_gotoxy (term, geo->first_entry_x - 1,
143 (geo->first_entry_y - 1 + geo->num_entries
144 + GRUB_TERM_MARGIN + 1));
145 }
146
147 static int
148 print_message (int nested, int edit, struct grub_term_output *term, int dry_run)
149 {
150 int ret = 0;
151 grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL);
152
153 if (edit)
154 {
155 ret += grub_print_message_indented_real (_("Minimum Emacs-like screen editing is \
156 supported. TAB lists completions. Press Ctrl-x or F10 to boot, Ctrl-c or F2 for a \
157 command-line or ESC to discard edits and return to the GRUB menu."),
158 STANDARD_MARGIN, STANDARD_MARGIN,
159 term, dry_run);
160 }
161 else
162 {
163 const char *msg = _("Use the %C and %C keys to select which "
164 "entry is highlighted.");
165 char *msg_translated;
166
167 msg_translated = grub_xasprintf (msg, GRUB_UNICODE_UPARROW,
168 GRUB_UNICODE_DOWNARROW);
169 if (!msg_translated)
170 return 0;
171 ret += grub_print_message_indented_real (msg_translated, STANDARD_MARGIN,
172 STANDARD_MARGIN, term, dry_run);
173
174 grub_free (msg_translated);
175
176 if (nested)
177 {
178 ret += grub_print_message_indented_real
179 (_("Press enter to boot the selected OS, "
180 "`e' to edit the commands before booting "
181 "or `c' for a command-line. ESC to return previous menu."),
182 STANDARD_MARGIN, STANDARD_MARGIN, term, dry_run);
183 }
184 else
185 {
186 ret += grub_print_message_indented_real
187 (_("Press enter to boot the selected OS, "
188 "`e' to edit the commands before booting "
189 "or `c' for a command-line."),
190 STANDARD_MARGIN, STANDARD_MARGIN, term, dry_run);
191 }
192 }
193 return ret;
194 }
195
196 static void
197 print_entry (int y, int highlight, grub_menu_entry_t entry,
198 const struct menu_viewer_data *data)
199 {
200 const char *title;
201 grub_size_t title_len;
202 grub_ssize_t len;
203 grub_uint32_t *unicode_title;
204 grub_ssize_t i;
205 grub_uint8_t old_color_normal, old_color_highlight;
206
207 title = entry ? entry->title : "";
208 title_len = grub_strlen (title);
209 unicode_title = grub_malloc (title_len * sizeof (*unicode_title));
210 if (! unicode_title)
211 /* XXX How to show this error? */
212 return;
213
214 len = grub_utf8_to_ucs4 (unicode_title, title_len,
215 (grub_uint8_t *) title, -1, 0);
216 if (len < 0)
217 {
218 /* It is an invalid sequence. */
219 grub_free (unicode_title);
220 return;
221 }
222
223 old_color_normal = grub_term_normal_color;
224 old_color_highlight = grub_term_highlight_color;
225 grub_term_normal_color = grub_color_menu_normal;
226 grub_term_highlight_color = grub_color_menu_highlight;
227 grub_term_setcolorstate (data->term, highlight
228 ? GRUB_TERM_COLOR_HIGHLIGHT
229 : GRUB_TERM_COLOR_NORMAL);
230
231 grub_term_gotoxy (data->term, data->geo.first_entry_x, y);
232
233 for (i = 0; i < len; i++)
234 if (unicode_title[i] == '\n' || unicode_title[i] == '\b'
235 || unicode_title[i] == '\r' || unicode_title[i] == '\e')
236 unicode_title[i] = ' ';
237
238 if (data->geo.num_entries > 1)
239 grub_putcode (highlight ? '*' : ' ', data->term);
240
241 grub_print_ucs4_menu (unicode_title,
242 unicode_title + len,
243 0,
244 data->geo.right_margin,
245 data->term, 0, 1,
246 GRUB_UNICODE_RIGHTARROW, 0);
247
248 grub_term_setcolorstate (data->term, GRUB_TERM_COLOR_NORMAL);
249 grub_term_gotoxy (data->term, grub_term_cursor_x (&data->geo), y);
250
251 grub_term_normal_color = old_color_normal;
252 grub_term_highlight_color = old_color_highlight;
253
254 grub_term_setcolorstate (data->term, GRUB_TERM_COLOR_NORMAL);
255 grub_free (unicode_title);
256 }
257
258 static void
259 print_entries (grub_menu_t menu, const struct menu_viewer_data *data)
260 {
261 grub_menu_entry_t e;
262 int i;
263
264 grub_term_gotoxy (data->term,
265 data->geo.first_entry_x + data->geo.entry_width
266 + data->geo.border + 1,
267 data->geo.first_entry_y);
268
269 if (data->geo.num_entries != 1)
270 {
271 if (data->first)
272 grub_putcode (GRUB_UNICODE_UPARROW, data->term);
273 else
274 grub_putcode (' ', data->term);
275 }
276 e = grub_menu_get_entry (menu, data->first);
277
278 for (i = 0; i < data->geo.num_entries; i++)
279 {
280 print_entry (data->geo.first_entry_y + i, data->offset == i,
281 e, data);
282 if (e)
283 e = e->next;
284 }
285
286 grub_term_gotoxy (data->term, data->geo.first_entry_x + data->geo.entry_width
287 + data->geo.border + 1,
288 data->geo.first_entry_y + data->geo.num_entries - 1);
289 if (data->geo.num_entries == 1)
290 {
291 if (data->first && e)
292 grub_putcode (GRUB_UNICODE_UPDOWNARROW, data->term);
293 else if (data->first)
294 grub_putcode (GRUB_UNICODE_UPARROW, data->term);
295 else if (e)
296 grub_putcode (GRUB_UNICODE_DOWNARROW, data->term);
297 else
298 grub_putcode (' ', data->term);
299 }
300 else
301 {
302 if (e)
303 grub_putcode (GRUB_UNICODE_DOWNARROW, data->term);
304 else
305 grub_putcode (' ', data->term);
306 }
307
308 grub_term_gotoxy (data->term, grub_term_cursor_x (&data->geo),
309 data->geo.first_entry_y + data->offset);
310 }
311
312 /* Initialize the screen. If NESTED is non-zero, assume that this menu
313 is run from another menu or a command-line. If EDIT is non-zero, show
314 a message for the menu entry editor. */
315 void
316 grub_menu_init_page (int nested, int edit,
317 struct grub_term_screen_geometry *geo,
318 struct grub_term_output *term)
319 {
320 grub_uint8_t old_color_normal, old_color_highlight;
321 int msg_num_lines;
322 int bottom_message = 1;
323 int empty_lines = 1;
324 int version_msg = 1;
325
326 geo->border = 1;
327 geo->first_entry_x = 1 /* margin */ + 1 /* border */;
328 geo->entry_width = grub_term_width (term) - 5;
329
330 geo->first_entry_y = 2 /* two empty lines*/
331 + 1 /* GNU GRUB version text */ + 1 /* top border */;
332
333 geo->timeout_lines = 2;
334
335 /* 3 lines for timeout message and bottom margin. 2 lines for the border. */
336 geo->num_entries = grub_term_height (term) - geo->first_entry_y
337 - 1 /* bottom border */
338 - 1 /* empty line before info message*/
339 - geo->timeout_lines /* timeout */
340 - 1 /* empty final line */;
341 msg_num_lines = print_message (nested, edit, term, 1);
342 if (geo->num_entries - msg_num_lines < 3
343 || geo->entry_width < 10)
344 {
345 geo->num_entries += 4;
346 geo->first_entry_y -= 2;
347 empty_lines = 0;
348 geo->first_entry_x -= 1;
349 geo->entry_width += 1;
350 }
351 if (geo->num_entries - msg_num_lines < 3
352 || geo->entry_width < 10)
353 {
354 geo->num_entries += 2;
355 geo->first_entry_y -= 1;
356 geo->first_entry_x -= 1;
357 geo->entry_width += 2;
358 geo->border = 0;
359 }
360
361 if (geo->num_entries - msg_num_lines < 3
362 && geo->timeout_lines == 2)
363 {
364 geo->timeout_lines = 1;
365 geo->num_entries++;
366 }
367
368 if (geo->num_entries - msg_num_lines < 3)
369 {
370 geo->num_entries += 1;
371 geo->first_entry_y -= 1;
372 version_msg = 0;
373 }
374
375 if (geo->num_entries - msg_num_lines >= 2)
376 geo->num_entries -= msg_num_lines;
377 else
378 bottom_message = 0;
379
380 /* By default, use the same colors for the menu. */
381 old_color_normal = grub_term_normal_color;
382 old_color_highlight = grub_term_highlight_color;
383 grub_color_menu_normal = grub_term_normal_color;
384 grub_color_menu_highlight = grub_term_highlight_color;
385
386 /* Then give user a chance to replace them. */
387 grub_parse_color_name_pair (&grub_color_menu_normal,
388 grub_env_get ("menu_color_normal"));
389 grub_parse_color_name_pair (&grub_color_menu_highlight,
390 grub_env_get ("menu_color_highlight"));
391
392 if (version_msg)
393 grub_normal_init_page (term, empty_lines);
394 else
395 grub_term_cls (term);
396
397 grub_term_normal_color = grub_color_menu_normal;
398 grub_term_highlight_color = grub_color_menu_highlight;
399 if (geo->border)
400 draw_border (term, geo);
401 grub_term_normal_color = old_color_normal;
402 grub_term_highlight_color = old_color_highlight;
403 geo->timeout_y = geo->first_entry_y + geo->num_entries
404 + geo->border + empty_lines;
405 if (bottom_message)
406 {
407 grub_term_gotoxy (term, GRUB_TERM_MARGIN,
408 geo->timeout_y);
409
410 print_message (nested, edit, term, 0);
411 geo->timeout_y += msg_num_lines;
412 }
413 geo->right_margin = grub_term_width (term)
414 - geo->first_entry_x
415 - geo->entry_width - 1;
416 }
417
418 static void
419 menu_text_print_timeout (int timeout, void *dataptr)
420 {
421 const char *msg =
422 _("The highlighted entry will be executed automatically in %ds.");
423 const char *msg_terse = _("%ds");
424 struct menu_viewer_data *data = dataptr;
425 char *msg_translated = 0;
426
427 grub_term_gotoxy (data->term, 0, data->geo.timeout_y);
428
429 if (data->timeout_msg == TIMEOUT_TERSE
430 || data->timeout_msg == TIMEOUT_TERSE_NO_MARGIN)
431 msg_translated = grub_xasprintf (msg_terse, timeout);
432 else
433 msg_translated = grub_xasprintf (msg, timeout);
434 if (!msg_translated)
435 {
436 grub_print_error ();
437 grub_errno = GRUB_ERR_NONE;
438 return;
439 }
440
441 if (data->timeout_msg == TIMEOUT_UNKNOWN)
442 {
443 data->timeout_msg = grub_print_message_indented_real (msg_translated,
444 3, 1, data->term, 1)
445 <= data->geo.timeout_lines ? TIMEOUT_NORMAL : TIMEOUT_TERSE;
446 if (data->timeout_msg == TIMEOUT_TERSE)
447 {
448 grub_free (msg_translated);
449 msg_translated = grub_xasprintf (msg_terse, timeout);
450 if (grub_term_width (data->term) < 10)
451 data->timeout_msg = TIMEOUT_TERSE_NO_MARGIN;
452 }
453 }
454
455 grub_print_message_indented (msg_translated,
456 data->timeout_msg == TIMEOUT_TERSE_NO_MARGIN ? 0 : 3,
457 data->timeout_msg == TIMEOUT_TERSE_NO_MARGIN ? 0 : 1,
458 data->term);
459 grub_free (msg_translated);
460
461 grub_term_gotoxy (data->term,
462 grub_term_cursor_x (&data->geo),
463 data->geo.first_entry_y + data->offset);
464 grub_term_refresh (data->term);
465 }
466
467 static void
468 menu_text_set_chosen_entry (int entry, void *dataptr)
469 {
470 struct menu_viewer_data *data = dataptr;
471 int oldoffset = data->offset;
472 int complete_redraw = 0;
473
474 data->offset = entry - data->first;
475 if (data->offset > data->geo.num_entries - 1)
476 {
477 data->first = entry - (data->geo.num_entries - 1);
478 data->offset = data->geo.num_entries - 1;
479 complete_redraw = 1;
480 }
481 if (data->offset < 0)
482 {
483 data->offset = 0;
484 data->first = entry;
485 complete_redraw = 1;
486 }
487 if (complete_redraw)
488 print_entries (data->menu, data);
489 else
490 {
491 print_entry (data->geo.first_entry_y + oldoffset, 0,
492 grub_menu_get_entry (data->menu, data->first + oldoffset),
493 data);
494 print_entry (data->geo.first_entry_y + data->offset, 1,
495 grub_menu_get_entry (data->menu, data->first + data->offset),
496 data);
497 }
498 grub_term_refresh (data->term);
499 }
500
501 static void
502 menu_text_fini (void *dataptr)
503 {
504 struct menu_viewer_data *data = dataptr;
505
506 grub_term_setcursor (data->term, 1);
507 grub_term_cls (data->term);
508 grub_free (data);
509 }
510
511 static void
512 menu_text_clear_timeout (void *dataptr)
513 {
514 struct menu_viewer_data *data = dataptr;
515 int i;
516
517 for (i = 0; i < data->geo.timeout_lines;i++)
518 {
519 grub_term_gotoxy (data->term, 0, data->geo.timeout_y + i);
520 grub_print_spaces (data->term, grub_term_width (data->term) - 1);
521 }
522 if (data->geo.num_entries <= 5 && !data->geo.border)
523 {
524 grub_term_gotoxy (data->term, data->geo.first_entry_x + data->geo.entry_width
525 + data->geo.border + 1,
526 data->geo.first_entry_y + data->geo.num_entries - 1);
527 grub_putcode (' ', data->term);
528
529 data->geo.timeout_lines = 0;
530 data->geo.num_entries++;
531 print_entries (data->menu, data);
532 }
533 grub_term_gotoxy (data->term, grub_term_cursor_x (&data->geo),
534 data->geo.first_entry_y + data->offset);
535 grub_term_refresh (data->term);
536 }
537
538 grub_err_t
539 grub_menu_try_text (struct grub_term_output *term,
540 int entry, grub_menu_t menu, int nested)
541 {
542 struct menu_viewer_data *data;
543 struct grub_menu_viewer *instance;
544
545 instance = grub_zalloc (sizeof (*instance));
546 if (!instance)
547 return grub_errno;
548
549 data = grub_zalloc (sizeof (*data));
550 if (!data)
551 {
552 grub_free (instance);
553 return grub_errno;
554 }
555
556 data->term = term;
557 instance->data = data;
558 instance->set_chosen_entry = menu_text_set_chosen_entry;
559 instance->print_timeout = menu_text_print_timeout;
560 instance->clear_timeout = menu_text_clear_timeout;
561 instance->fini = menu_text_fini;
562
563 data->menu = menu;
564
565 data->offset = entry;
566 data->first = 0;
567
568 grub_term_setcursor (data->term, 0);
569 grub_menu_init_page (nested, 0, &data->geo, data->term);
570
571 if (data->offset > data->geo.num_entries - 1)
572 {
573 data->first = data->offset - (data->geo.num_entries - 1);
574 data->offset = data->geo.num_entries - 1;
575 }
576
577 print_entries (menu, data);
578 grub_term_refresh (data->term);
579 grub_menu_register_viewer (instance);
580
581 return GRUB_ERR_NONE;
582 }