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