]> git.proxmox.com Git - grub2.git/blob - grub-core/normal/menu_text.c
* grub-core/normal/menu_text.c (grub_menu_init_page): Fix behaviour
[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 /* The number of entries shown at a time. */
38 int num_entries;
39 grub_menu_t menu;
40 struct grub_term_output *term;
41 };
42
43 static inline int
44 grub_term_cursor_x (struct grub_term_output *term)
45 {
46 return (GRUB_TERM_LEFT_BORDER_X + grub_term_border_width (term)
47 - GRUB_TERM_MARGIN - 1);
48 }
49
50 grub_ssize_t
51 grub_getstringwidth (grub_uint32_t * str, const grub_uint32_t * last_position,
52 struct grub_term_output *term)
53 {
54 grub_ssize_t width = 0;
55
56 while (str < last_position)
57 {
58 struct grub_unicode_glyph glyph;
59 str += grub_unicode_aglomerate_comb (str, last_position - str, &glyph);
60 width += grub_term_getcharwidth (term, &glyph);
61 }
62 return width;
63 }
64
65 static int
66 grub_print_message_indented_real (const char *msg, int margin_left,
67 int margin_right,
68 struct grub_term_output *term, int dry_run)
69 {
70 grub_uint32_t *unicode_msg;
71 grub_uint32_t *last_position;
72 grub_size_t msg_len = grub_strlen (msg) + 2;
73 int ret = 0;
74
75 unicode_msg = grub_malloc (msg_len * sizeof (grub_uint32_t));
76
77 if (!unicode_msg)
78 return 0;
79
80 msg_len = grub_utf8_to_ucs4 (unicode_msg, msg_len,
81 (grub_uint8_t *) msg, -1, 0);
82
83 last_position = unicode_msg + msg_len;
84 *last_position++ = '\n';
85 *last_position = 0;
86
87 if (dry_run)
88 ret = grub_ucs4_count_lines (unicode_msg, last_position, margin_left,
89 margin_right, term);
90 else
91 grub_print_ucs4 (unicode_msg, last_position, margin_left,
92 margin_right, term);
93
94 grub_free (unicode_msg);
95
96 return ret;
97 }
98
99 void
100 grub_print_message_indented (const char *msg, int margin_left, int margin_right,
101 struct grub_term_output *term)
102 {
103 grub_print_message_indented_real (msg, margin_left, margin_right, term, 0);
104 }
105
106 static void
107 draw_border (struct grub_term_output *term, int num_entries)
108 {
109 unsigned i;
110
111 grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL);
112
113 grub_term_gotoxy (term, GRUB_TERM_MARGIN, GRUB_TERM_TOP_BORDER_Y);
114 grub_putcode (GRUB_UNICODE_CORNER_UL, term);
115 for (i = 0; i < (unsigned) grub_term_border_width (term) - 2; i++)
116 grub_putcode (GRUB_UNICODE_HLINE, term);
117 grub_putcode (GRUB_UNICODE_CORNER_UR, term);
118
119 for (i = 0; i < (unsigned) num_entries; i++)
120 {
121 grub_term_gotoxy (term, GRUB_TERM_MARGIN, GRUB_TERM_TOP_BORDER_Y + i + 1);
122 grub_putcode (GRUB_UNICODE_VLINE, term);
123 grub_term_gotoxy (term, GRUB_TERM_MARGIN + grub_term_border_width (term)
124 - 1,
125 GRUB_TERM_TOP_BORDER_Y + i + 1);
126 grub_putcode (GRUB_UNICODE_VLINE, term);
127 }
128
129 grub_term_gotoxy (term, GRUB_TERM_MARGIN,
130 GRUB_TERM_TOP_BORDER_Y + num_entries + 1);
131 grub_putcode (GRUB_UNICODE_CORNER_LL, term);
132 for (i = 0; i < (unsigned) grub_term_border_width (term) - 2; i++)
133 grub_putcode (GRUB_UNICODE_HLINE, term);
134 grub_putcode (GRUB_UNICODE_CORNER_LR, term);
135
136 grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL);
137
138 grub_term_gotoxy (term, GRUB_TERM_MARGIN,
139 (GRUB_TERM_TOP_BORDER_Y + num_entries
140 + GRUB_TERM_MARGIN + 1));
141 }
142
143 static int
144 print_message (int nested, int edit, struct grub_term_output *term, int dry_run)
145 {
146 int ret = 0;
147 grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL);
148
149 if (edit)
150 {
151 if(dry_run)
152 ret++;
153 else
154 grub_putcode ('\n', term);
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 if(dry_run)
172 ret++;
173 else
174 grub_putcode ('\n', term);
175 ret += grub_print_message_indented_real (msg_translated, STANDARD_MARGIN,
176 STANDARD_MARGIN, term, dry_run);
177
178 grub_free (msg_translated);
179
180 if (nested)
181 {
182 ret += grub_print_message_indented_real
183 (_("Press enter to boot the selected OS, "
184 "`e' to edit the commands before booting "
185 "or `c' for a command-line. ESC to return previous menu."),
186 STANDARD_MARGIN, STANDARD_MARGIN, term, dry_run);
187 }
188 else
189 {
190 ret += grub_print_message_indented_real
191 (_("Press enter to boot the selected OS, "
192 "`e' to edit the commands before booting "
193 "or `c' for a command-line."),
194 STANDARD_MARGIN, STANDARD_MARGIN, term, dry_run);
195 }
196 }
197 return ret;
198 }
199
200 static void
201 print_entry (int y, int highlight, grub_menu_entry_t entry,
202 struct grub_term_output *term)
203 {
204 int x;
205 const char *title;
206 grub_size_t title_len;
207 grub_ssize_t len;
208 grub_uint32_t *unicode_title;
209 grub_ssize_t i;
210 grub_uint8_t old_color_normal, old_color_highlight;
211
212 title = entry ? entry->title : "";
213 title_len = grub_strlen (title);
214 unicode_title = grub_malloc (title_len * sizeof (*unicode_title));
215 if (! unicode_title)
216 /* XXX How to show this error? */
217 return;
218
219 len = grub_utf8_to_ucs4 (unicode_title, title_len,
220 (grub_uint8_t *) title, -1, 0);
221 if (len < 0)
222 {
223 /* It is an invalid sequence. */
224 grub_free (unicode_title);
225 return;
226 }
227
228 old_color_normal = grub_term_normal_color;
229 old_color_highlight = grub_term_highlight_color;
230 grub_term_normal_color = grub_color_menu_normal;
231 grub_term_highlight_color = grub_color_menu_highlight;
232 grub_term_setcolorstate (term, highlight
233 ? GRUB_TERM_COLOR_HIGHLIGHT
234 : GRUB_TERM_COLOR_NORMAL);
235
236 grub_term_gotoxy (term, GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_MARGIN, y);
237
238 int last_printed = 0;
239
240 for (i = 0; i < len; i++)
241 if (unicode_title[i] == '\n' || unicode_title[i] == '\b'
242 || unicode_title[i] == '\r' || unicode_title[i] == '\e')
243 unicode_title[i] = ' ';
244
245 for (x = GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_MARGIN + 1, i = 0;
246 x < (int) (GRUB_TERM_LEFT_BORDER_X + grub_term_border_width (term)
247 - GRUB_TERM_MARGIN);)
248 {
249 if (i < len
250 && x <= (int) (GRUB_TERM_LEFT_BORDER_X + grub_term_border_width (term)
251 - GRUB_TERM_MARGIN - 1))
252 {
253 grub_ssize_t width;
254 struct grub_unicode_glyph glyph;
255
256 i += grub_unicode_aglomerate_comb (unicode_title + i,
257 len - i, &glyph);
258
259 width = grub_term_getcharwidth (term, &glyph);
260 grub_free (glyph.combining);
261
262 if (x + width <= (int) (GRUB_TERM_LEFT_BORDER_X
263 + grub_term_border_width (term)
264 - GRUB_TERM_MARGIN - 1))
265 last_printed = i;
266 x += width;
267 }
268 else
269 break;
270 }
271
272 grub_print_ucs4 (unicode_title,
273 unicode_title + last_printed, 0, 0, term);
274
275 if (last_printed != len)
276 {
277 grub_putcode (GRUB_UNICODE_RIGHTARROW, term);
278 struct grub_unicode_glyph pseudo_glyph = {
279 .base = GRUB_UNICODE_RIGHTARROW,
280 .variant = 0,
281 .attributes = 0,
282 .ncomb = 0,
283 .combining = 0,
284 .estimated_width = 1
285 };
286 x += grub_term_getcharwidth (term, &pseudo_glyph);
287 }
288
289 for (; x < (int) (GRUB_TERM_LEFT_BORDER_X + grub_term_border_width (term)
290 - GRUB_TERM_MARGIN); x++)
291 grub_putcode (' ', term);
292
293 grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL);
294 grub_putcode (' ', term);
295
296 grub_term_gotoxy (term, grub_term_cursor_x (term), y);
297
298 grub_term_normal_color = old_color_normal;
299 grub_term_highlight_color = old_color_highlight;
300
301 grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL);
302 grub_free (unicode_title);
303 }
304
305 static void
306 print_entries (grub_menu_t menu, const struct menu_viewer_data *data)
307 {
308 grub_menu_entry_t e;
309 int i;
310
311 grub_term_gotoxy (data->term,
312 GRUB_TERM_LEFT_BORDER_X + grub_term_border_width (data->term),
313 GRUB_TERM_FIRST_ENTRY_Y);
314
315 if (data->first)
316 grub_putcode (GRUB_UNICODE_UPARROW, data->term);
317 else
318 grub_putcode (' ', data->term);
319
320 e = grub_menu_get_entry (menu, data->first);
321
322 for (i = 0; i < data->num_entries; i++)
323 {
324 print_entry (GRUB_TERM_FIRST_ENTRY_Y + i, data->offset == i,
325 e, data->term);
326 if (e)
327 e = e->next;
328 }
329
330 grub_term_gotoxy (data->term, GRUB_TERM_LEFT_BORDER_X
331 + grub_term_border_width (data->term),
332 GRUB_TERM_TOP_BORDER_Y + data->num_entries);
333
334 if (e)
335 grub_putcode (GRUB_UNICODE_DOWNARROW, data->term);
336 else
337 grub_putcode (' ', data->term);
338
339 grub_term_gotoxy (data->term, grub_term_cursor_x (data->term),
340 GRUB_TERM_FIRST_ENTRY_Y + data->offset);
341 }
342
343 /* Initialize the screen. If NESTED is non-zero, assume that this menu
344 is run from another menu or a command-line. If EDIT is non-zero, show
345 a message for the menu entry editor. */
346 void
347 grub_menu_init_page (int nested, int edit, int *num_entries,
348 struct grub_term_output *term)
349 {
350 grub_uint8_t old_color_normal, old_color_highlight;
351
352 /* 3 lines for timeout message and bottom margin. 2 lines for the border. */
353 *num_entries = grub_term_height (term) - GRUB_TERM_TOP_BORDER_Y
354 - (print_message (nested, edit, term, 1) + 3) - 2;
355
356 /* By default, use the same colors for the menu. */
357 old_color_normal = grub_term_normal_color;
358 old_color_highlight = grub_term_highlight_color;
359 grub_color_menu_normal = grub_term_normal_color;
360 grub_color_menu_highlight = grub_term_highlight_color;
361
362 /* Then give user a chance to replace them. */
363 grub_parse_color_name_pair (&grub_color_menu_normal,
364 grub_env_get ("menu_color_normal"));
365 grub_parse_color_name_pair (&grub_color_menu_highlight,
366 grub_env_get ("menu_color_highlight"));
367
368 grub_normal_init_page (term);
369 grub_term_normal_color = grub_color_menu_normal;
370 grub_term_highlight_color = grub_color_menu_highlight;
371 draw_border (term, *num_entries);
372 grub_term_normal_color = old_color_normal;
373 grub_term_highlight_color = old_color_highlight;
374 print_message (nested, edit, term, 0);
375 }
376
377 static void
378 menu_text_print_timeout (int timeout, void *dataptr)
379 {
380 const char *msg =
381 _("The highlighted entry will be executed automatically in %ds.");
382 struct menu_viewer_data *data = dataptr;
383 char *msg_translated;
384 int posx;
385
386 grub_term_gotoxy (data->term, 0, grub_term_height (data->term) - 3);
387
388 msg_translated = grub_xasprintf (msg, timeout);
389 if (!msg_translated)
390 {
391 grub_print_error ();
392 grub_errno = GRUB_ERR_NONE;
393 return;
394 }
395
396 grub_print_message_indented (msg_translated, 3, 0, data->term);
397
398 posx = grub_term_getxy (data->term) >> 8;
399 grub_print_spaces (data->term, grub_term_width (data->term) - posx - 1);
400
401 grub_term_gotoxy (data->term,
402 grub_term_cursor_x (data->term),
403 GRUB_TERM_FIRST_ENTRY_Y + data->offset);
404 grub_term_refresh (data->term);
405 }
406
407 static void
408 menu_text_set_chosen_entry (int entry, void *dataptr)
409 {
410 struct menu_viewer_data *data = dataptr;
411 int oldoffset = data->offset;
412 int complete_redraw = 0;
413
414 data->offset = entry - data->first;
415 if (data->offset > data->num_entries - 1)
416 {
417 data->first = entry - (data->num_entries - 1);
418 data->offset = data->num_entries - 1;
419 complete_redraw = 1;
420 }
421 if (data->offset < 0)
422 {
423 data->offset = 0;
424 data->first = entry;
425 complete_redraw = 1;
426 }
427 if (complete_redraw)
428 print_entries (data->menu, data);
429 else
430 {
431 print_entry (GRUB_TERM_FIRST_ENTRY_Y + oldoffset, 0,
432 grub_menu_get_entry (data->menu, data->first + oldoffset),
433 data->term);
434 print_entry (GRUB_TERM_FIRST_ENTRY_Y + data->offset, 1,
435 grub_menu_get_entry (data->menu, data->first + data->offset),
436 data->term);
437 }
438 grub_term_refresh (data->term);
439 }
440
441 static void
442 menu_text_fini (void *dataptr)
443 {
444 struct menu_viewer_data *data = dataptr;
445
446 grub_term_setcursor (data->term, 1);
447 grub_term_cls (data->term);
448
449 }
450
451 static void
452 menu_text_clear_timeout (void *dataptr)
453 {
454 struct menu_viewer_data *data = dataptr;
455
456 grub_term_gotoxy (data->term, 0, grub_term_height (data->term) - 3);
457 grub_print_spaces (data->term, grub_term_width (data->term) - 1);
458 grub_term_gotoxy (data->term, grub_term_cursor_x (data->term),
459 GRUB_TERM_FIRST_ENTRY_Y + data->offset);
460 grub_term_refresh (data->term);
461 }
462
463 grub_err_t
464 grub_menu_try_text (struct grub_term_output *term,
465 int entry, grub_menu_t menu, int nested)
466 {
467 struct menu_viewer_data *data;
468 struct grub_menu_viewer *instance;
469
470 instance = grub_zalloc (sizeof (*instance));
471 if (!instance)
472 return grub_errno;
473
474 data = grub_zalloc (sizeof (*data));
475 if (!data)
476 {
477 grub_free (instance);
478 return grub_errno;
479 }
480
481 data->term = term;
482 instance->data = data;
483 instance->set_chosen_entry = menu_text_set_chosen_entry;
484 instance->print_timeout = menu_text_print_timeout;
485 instance->clear_timeout = menu_text_clear_timeout;
486 instance->fini = menu_text_fini;
487
488 data->menu = menu;
489
490 data->offset = entry;
491 data->first = 0;
492
493 grub_term_setcursor (data->term, 0);
494 grub_menu_init_page (nested, 0, &data->num_entries, data->term);
495
496 if (data->offset > data->num_entries - 1)
497 {
498 data->first = data->offset - (data->num_entries - 1);
499 data->offset = data->num_entries - 1;
500 }
501
502 print_entries (menu, data);
503 grub_term_refresh (data->term);
504 grub_menu_register_viewer (instance);
505
506 return GRUB_ERR_NONE;
507 }