1 /* menu_text.c - Basic text menu implementation. */
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2003,2004,2005,2006,2007,2008,2009 Free Software Foundation, Inc.
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.
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.
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/>.
20 #include <grub/normal.h>
21 #include <grub/term.h>
22 #include <grub/misc.h>
23 #include <grub/loader.h>
25 #include <grub/time.h>
27 #include <grub/menu_viewer.h>
28 #include <grub/i18n.h>
29 #include <grub/charset.h>
31 static grub_uint8_t grub_color_menu_normal
;
32 static grub_uint8_t grub_color_menu_highlight
;
34 struct menu_viewer_data
37 struct grub_term_screen_geometry geo
;
42 TIMEOUT_TERSE_NO_MARGIN
45 struct grub_term_output
*term
;
49 grub_term_cursor_x (const struct grub_term_screen_geometry
*geo
)
51 return (geo
->first_entry_x
+ geo
->entry_width
);
55 grub_getstringwidth (grub_uint32_t
* str
, const grub_uint32_t
* last_position
,
56 struct grub_term_output
*term
)
58 grub_ssize_t width
= 0;
60 while (str
< last_position
)
62 struct grub_unicode_glyph glyph
;
64 str
+= grub_unicode_aglomerate_comb (str
, last_position
- str
, &glyph
);
65 width
+= grub_term_getcharwidth (term
, &glyph
);
66 grub_unicode_destroy_glyph (&glyph
);
72 grub_print_message_indented_real (const char *msg
, int margin_left
,
74 struct grub_term_output
*term
, int dry_run
)
76 grub_uint32_t
*unicode_msg
;
77 grub_uint32_t
*last_position
;
78 grub_size_t msg_len
= grub_strlen (msg
) + 2;
81 unicode_msg
= grub_malloc (msg_len
* sizeof (grub_uint32_t
));
86 msg_len
= grub_utf8_to_ucs4 (unicode_msg
, msg_len
,
87 (grub_uint8_t
*) msg
, -1, 0);
89 last_position
= unicode_msg
+ msg_len
;
93 ret
= grub_ucs4_count_lines (unicode_msg
, last_position
, margin_left
,
96 grub_print_ucs4_menu (unicode_msg
, last_position
, margin_left
,
97 margin_right
, term
, 0, -1, 0, 0);
99 grub_free (unicode_msg
);
105 grub_print_message_indented (const char *msg
, int margin_left
, int margin_right
,
106 struct grub_term_output
*term
)
108 grub_print_message_indented_real (msg
, margin_left
, margin_right
, term
, 0);
112 draw_border (struct grub_term_output
*term
, const struct grub_term_screen_geometry
*geo
)
116 grub_term_setcolorstate (term
, GRUB_TERM_COLOR_NORMAL
);
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
);
125 for (i
= 0; i
< geo
->num_entries
; i
++)
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
);
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
);
144 grub_term_setcolorstate (term
, GRUB_TERM_COLOR_NORMAL
);
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) });
153 print_message (int nested
, int edit
, struct grub_term_output
*term
, int dry_run
)
156 grub_term_setcolorstate (term
, GRUB_TERM_COLOR_NORMAL
);
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
,
168 char *msg_translated
;
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
);
176 ret
+= grub_print_message_indented_real (msg_translated
, STANDARD_MARGIN
,
177 STANDARD_MARGIN
, term
, dry_run
);
179 grub_free (msg_translated
);
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
);
193 ret
+= grub_print_message_indented_real("\n", STANDARD_MARGIN
, STANDARD_MARGIN
, term
, dry_run
);
195 ret
+= grub_print_message_indented_real(grub_env_get("VTOY_TEXT_MENU_VER"),
196 STANDARD_MARGIN
, STANDARD_MARGIN
, term
, dry_run
);
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
);
207 print_entry (int y
, int highlight
, grub_menu_entry_t entry
,
208 const struct menu_viewer_data
*data
)
211 grub_size_t title_len
;
213 grub_uint32_t
*unicode_title
;
215 grub_uint8_t old_color_normal
, old_color_highlight
;
217 title
= entry
? entry
->title
: "";
218 title_len
= grub_strlen (title
);
219 unicode_title
= grub_malloc (title_len
* sizeof (*unicode_title
));
221 /* XXX How to show this error? */
224 len
= grub_utf8_to_ucs4 (unicode_title
, title_len
,
225 (grub_uint8_t
*) title
, -1, 0);
228 /* It is an invalid sequence. */
229 grub_free (unicode_title
);
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
);
241 grub_term_gotoxy (data
->term
, (struct grub_term_coordinate
) {
242 data
->geo
.first_entry_x
, y
});
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
] = ' ';
249 if (data
->geo
.num_entries
> 1)
250 grub_putcode (highlight
? '*' : ' ', data
->term
);
252 grub_print_ucs4_menu (unicode_title
,
255 data
->geo
.right_margin
,
257 GRUB_UNICODE_RIGHTARROW
, 0);
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
});
264 grub_term_normal_color
= old_color_normal
;
265 grub_term_highlight_color
= old_color_highlight
;
267 grub_term_setcolorstate (data
->term
, GRUB_TERM_COLOR_NORMAL
);
268 grub_free (unicode_title
);
272 print_entries (grub_menu_t menu
, const struct menu_viewer_data
*data
)
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
});
283 if (data
->geo
.num_entries
!= 1)
286 grub_putcode (GRUB_UNICODE_UPARROW
, data
->term
);
288 grub_putcode (' ', data
->term
);
290 e
= grub_menu_get_entry (menu
, data
->first
);
292 for (i
= 0; i
< data
->geo
.num_entries
; i
++)
294 print_entry (data
->geo
.first_entry_y
+ i
, data
->offset
== i
,
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)
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
);
311 grub_putcode (GRUB_UNICODE_DOWNARROW
, data
->term
);
313 grub_putcode (' ', data
->term
);
318 grub_putcode (GRUB_UNICODE_DOWNARROW
, data
->term
);
320 grub_putcode (' ', data
->term
);
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
});
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. */
332 grub_menu_init_page (int nested
, int edit
,
333 struct grub_term_screen_geometry
*geo
,
334 struct grub_term_output
*term
)
336 grub_uint8_t old_color_normal
, old_color_highlight
;
338 int bottom_message
= 1;
343 geo
->first_entry_x
= 1 /* margin */ + 1 /* border */;
344 geo
->entry_width
= grub_term_width (term
) - 5;
346 geo
->first_entry_y
= 2 /* two empty lines*/
347 + 1 /* GNU GRUB version text */ + 1 /* top border */;
349 geo
->timeout_lines
= 2;
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)
361 geo
->num_entries
+= 4;
362 geo
->first_entry_y
-= 2;
364 geo
->first_entry_x
-= 1;
365 geo
->entry_width
+= 1;
367 if (geo
->num_entries
- msg_num_lines
< 3
368 || geo
->entry_width
< 10)
370 geo
->num_entries
+= 2;
371 geo
->first_entry_y
-= 1;
372 geo
->first_entry_x
-= 1;
373 geo
->entry_width
+= 2;
377 if (geo
->entry_width
<= 0)
378 geo
->entry_width
= 1;
380 if (geo
->num_entries
- msg_num_lines
< 3
381 && geo
->timeout_lines
== 2)
383 geo
->timeout_lines
= 1;
387 if (geo
->num_entries
- msg_num_lines
< 3)
389 geo
->num_entries
+= 1;
390 geo
->first_entry_y
-= 1;
394 if (geo
->num_entries
- msg_num_lines
>= 2)
395 geo
->num_entries
-= msg_num_lines
;
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
;
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"));
412 grub_normal_init_page (term
, empty_lines
);
414 grub_term_cls (term
);
416 grub_term_normal_color
= grub_color_menu_normal
;
417 grub_term_highlight_color
= grub_color_menu_highlight
;
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
;
426 grub_term_gotoxy (term
,
427 (struct grub_term_coordinate
) { GRUB_TERM_MARGIN
,
430 print_message (nested
, edit
, term
, 0);
431 geo
->timeout_y
+= msg_num_lines
;
433 geo
->right_margin
= grub_term_width (term
)
435 - geo
->entry_width
- 1;
439 menu_text_print_timeout (int timeout
, void *dataptr
)
441 struct menu_viewer_data
*data
= dataptr
;
442 char *msg_translated
= 0;
444 grub_term_gotoxy (data
->term
,
445 (struct grub_term_coordinate
) { 0, data
->geo
.timeout_y
});
447 if (data
->timeout_msg
== TIMEOUT_TERSE
448 || data
->timeout_msg
== TIMEOUT_TERSE_NO_MARGIN
)
449 msg_translated
= grub_xasprintf (_("%ds"), timeout
);
451 msg_translated
= grub_xasprintf (_("The highlighted entry will be executed automatically in %ds."), timeout
);
455 grub_errno
= GRUB_ERR_NONE
;
459 if (data
->timeout_msg
== TIMEOUT_UNKNOWN
)
461 data
->timeout_msg
= grub_print_message_indented_real (msg_translated
,
463 <= data
->geo
.timeout_lines
? TIMEOUT_NORMAL
: TIMEOUT_TERSE
;
464 if (data
->timeout_msg
== TIMEOUT_TERSE
)
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
;
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,
477 grub_free (msg_translated
);
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
);
487 menu_text_set_chosen_entry (int entry
, void *dataptr
)
489 struct menu_viewer_data
*data
= dataptr
;
490 int oldoffset
= data
->offset
;
491 int complete_redraw
= 0;
493 data
->offset
= entry
- data
->first
;
494 if (data
->offset
> data
->geo
.num_entries
- 1)
496 data
->first
= entry
- (data
->geo
.num_entries
- 1);
497 data
->offset
= data
->geo
.num_entries
- 1;
500 if (data
->offset
< 0)
507 print_entries (data
->menu
, data
);
510 print_entry (data
->geo
.first_entry_y
+ oldoffset
, 0,
511 grub_menu_get_entry (data
->menu
, data
->first
+ oldoffset
),
513 print_entry (data
->geo
.first_entry_y
+ data
->offset
, 1,
514 grub_menu_get_entry (data
->menu
, data
->first
+ data
->offset
),
517 grub_term_refresh (data
->term
);
521 menu_text_fini (void *dataptr
)
523 struct menu_viewer_data
*data
= dataptr
;
525 grub_term_setcursor (data
->term
, 1);
526 grub_term_cls (data
->term
);
531 menu_text_clear_timeout (void *dataptr
)
533 struct menu_viewer_data
*data
= dataptr
;
536 for (i
= 0; i
< data
->geo
.timeout_lines
;i
++)
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);
542 if (data
->geo
.num_entries
<= 5 && !data
->geo
.border
)
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
550 grub_putcode (' ', data
->term
);
552 data
->geo
.timeout_lines
= 0;
553 data
->geo
.num_entries
++;
554 print_entries (data
->menu
, data
);
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
);
564 grub_menu_try_text (struct grub_term_output
*term
,
565 int entry
, grub_menu_t menu
, int nested
)
567 struct menu_viewer_data
*data
;
568 struct grub_menu_viewer
*instance
;
570 instance
= grub_zalloc (sizeof (*instance
));
574 data
= grub_zalloc (sizeof (*data
));
577 grub_free (instance
);
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
;
590 data
->offset
= entry
;
593 grub_term_setcursor (data
->term
, 0);
594 grub_menu_init_page (nested
, 0, &data
->geo
, data
->term
);
596 if (data
->offset
> data
->geo
.num_entries
- 1)
598 data
->first
= data
->offset
- (data
->geo
.num_entries
- 1);
599 data
->offset
= data
->geo
.num_entries
- 1;
602 print_entries (menu
, data
);
603 grub_term_refresh (data
->term
);
604 grub_menu_register_viewer (instance
);
606 return GRUB_ERR_NONE
;