diff options
Diffstat (limited to 'grub-core/normal/menu_text.c')
-rw-r--r-- | grub-core/normal/menu_text.c | 602 |
1 files changed, 602 insertions, 0 deletions
diff --git a/grub-core/normal/menu_text.c b/grub-core/normal/menu_text.c new file mode 100644 index 0000000..18240e7 --- /dev/null +++ b/grub-core/normal/menu_text.c @@ -0,0 +1,602 @@ +/* menu_text.c - Basic text menu implementation. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2003,2004,2005,2006,2007,2008,2009 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/normal.h> +#include <grub/term.h> +#include <grub/misc.h> +#include <grub/loader.h> +#include <grub/mm.h> +#include <grub/time.h> +#include <grub/env.h> +#include <grub/menu_viewer.h> +#include <grub/i18n.h> +#include <grub/charset.h> + +static grub_uint8_t grub_color_menu_normal; +static grub_uint8_t grub_color_menu_highlight; + +struct menu_viewer_data +{ + int first, offset; + struct grub_term_screen_geometry geo; + enum { + TIMEOUT_UNKNOWN, + TIMEOUT_NORMAL, + TIMEOUT_TERSE, + TIMEOUT_TERSE_NO_MARGIN + } timeout_msg; + grub_menu_t menu; + struct grub_term_output *term; +}; + +static inline int +grub_term_cursor_x (const struct grub_term_screen_geometry *geo) +{ + return (geo->first_entry_x + geo->entry_width); +} + +grub_size_t +grub_getstringwidth (grub_uint32_t * str, const grub_uint32_t * last_position, + struct grub_term_output *term) +{ + grub_ssize_t width = 0; + + while (str < last_position) + { + struct grub_unicode_glyph glyph; + glyph.ncomb = 0; + str += grub_unicode_aglomerate_comb (str, last_position - str, &glyph); + width += grub_term_getcharwidth (term, &glyph); + grub_unicode_destroy_glyph (&glyph); + } + return width; +} + +static int +grub_print_message_indented_real (const char *msg, int margin_left, + int margin_right, + struct grub_term_output *term, int dry_run) +{ + grub_uint32_t *unicode_msg; + grub_uint32_t *last_position; + grub_size_t msg_len = grub_strlen (msg) + 2; + int ret = 0; + + unicode_msg = grub_calloc (msg_len, sizeof (grub_uint32_t)); + + if (!unicode_msg) + return 0; + + msg_len = grub_utf8_to_ucs4 (unicode_msg, msg_len, + (grub_uint8_t *) msg, -1, 0); + + last_position = unicode_msg + msg_len; + *last_position = 0; + + if (dry_run) + ret = grub_ucs4_count_lines (unicode_msg, last_position, margin_left, + margin_right, term); + else + grub_print_ucs4_menu (unicode_msg, last_position, margin_left, + margin_right, term, 0, -1, 0, 0); + + grub_free (unicode_msg); + + return ret; +} + +void +grub_print_message_indented (const char *msg, int margin_left, int margin_right, + struct grub_term_output *term) +{ + grub_print_message_indented_real (msg, margin_left, margin_right, term, 0); +} + +static void +draw_border (struct grub_term_output *term, const struct grub_term_screen_geometry *geo) +{ + int i; + + grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL); + + grub_term_gotoxy (term, (struct grub_term_coordinate) { geo->first_entry_x - 1, + geo->first_entry_y - 1 }); + grub_putcode (GRUB_UNICODE_CORNER_UL, term); + for (i = 0; i < geo->entry_width + 1; i++) + grub_putcode (GRUB_UNICODE_HLINE, term); + grub_putcode (GRUB_UNICODE_CORNER_UR, term); + + for (i = 0; i < geo->num_entries; i++) + { + grub_term_gotoxy (term, (struct grub_term_coordinate) { geo->first_entry_x - 1, + geo->first_entry_y + i }); + grub_putcode (GRUB_UNICODE_VLINE, term); + grub_term_gotoxy (term, + (struct grub_term_coordinate) { geo->first_entry_x + geo->entry_width + 1, + geo->first_entry_y + i }); + grub_putcode (GRUB_UNICODE_VLINE, term); + } + + grub_term_gotoxy (term, + (struct grub_term_coordinate) { geo->first_entry_x - 1, + geo->first_entry_y - 1 + geo->num_entries + 1 }); + grub_putcode (GRUB_UNICODE_CORNER_LL, term); + for (i = 0; i < geo->entry_width + 1; i++) + grub_putcode (GRUB_UNICODE_HLINE, term); + grub_putcode (GRUB_UNICODE_CORNER_LR, term); + + grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL); + + grub_term_gotoxy (term, + (struct grub_term_coordinate) { geo->first_entry_x - 1, + (geo->first_entry_y - 1 + geo->num_entries + + GRUB_TERM_MARGIN + 1) }); +} + +static int +print_message (int nested, int edit, struct grub_term_output *term, int dry_run) +{ + int ret = 0; + grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL); + + if (edit) + { + ret += grub_print_message_indented_real (_("Minimum Emacs-like screen editing is \ +supported. TAB lists completions. Press Ctrl-x or F10 to boot, Ctrl-c or F2 for a \ +command-line or ESC to discard edits and return to the GRUB menu."), + STANDARD_MARGIN, STANDARD_MARGIN, + term, dry_run); + } + else + { + char *msg_translated; + + msg_translated = grub_xasprintf (_("Use the %C and %C keys to select which " + "entry is highlighted."), + GRUB_UNICODE_UPARROW, + GRUB_UNICODE_DOWNARROW); + if (!msg_translated) + return 0; + ret += grub_print_message_indented_real (msg_translated, STANDARD_MARGIN, + STANDARD_MARGIN, term, dry_run); + + grub_free (msg_translated); + + if (nested) + { + ret += grub_print_message_indented_real + (_("Press enter to boot the selected OS, " + "`e' to edit the commands before booting " + "or `c' for a command-line. ESC to return previous menu."), + STANDARD_MARGIN, STANDARD_MARGIN, term, dry_run); + } + else + { + ret += grub_print_message_indented_real + (_("Press enter to boot the selected OS, " + "`e' to edit the commands before booting " + "or `c' for a command-line."), + STANDARD_MARGIN, STANDARD_MARGIN, term, dry_run); + } + } + return ret; +} + +static void +print_entry (int y, int highlight, grub_menu_entry_t entry, + const struct menu_viewer_data *data) +{ + const char *title; + grub_size_t title_len; + grub_ssize_t len; + grub_uint32_t *unicode_title; + grub_ssize_t i; + grub_uint8_t old_color_normal, old_color_highlight; + + title = entry ? entry->title : ""; + title_len = grub_strlen (title); + unicode_title = grub_calloc (title_len, sizeof (*unicode_title)); + if (! unicode_title) + /* XXX How to show this error? */ + return; + + len = grub_utf8_to_ucs4 (unicode_title, title_len, + (grub_uint8_t *) title, -1, 0); + if (len < 0) + { + /* It is an invalid sequence. */ + grub_free (unicode_title); + return; + } + + old_color_normal = grub_term_normal_color; + old_color_highlight = grub_term_highlight_color; + grub_term_normal_color = grub_color_menu_normal; + grub_term_highlight_color = grub_color_menu_highlight; + grub_term_setcolorstate (data->term, highlight + ? GRUB_TERM_COLOR_HIGHLIGHT + : GRUB_TERM_COLOR_NORMAL); + + grub_term_gotoxy (data->term, (struct grub_term_coordinate) { + data->geo.first_entry_x, y }); + + for (i = 0; i < len; i++) + if (unicode_title[i] == '\n' || unicode_title[i] == '\b' + || unicode_title[i] == '\r' || unicode_title[i] == '\e') + unicode_title[i] = ' '; + + if (data->geo.num_entries > 1) + grub_putcode (highlight ? '*' : ' ', data->term); + + grub_print_ucs4_menu (unicode_title, + unicode_title + len, + 0, + data->geo.right_margin, + data->term, 0, 1, + GRUB_UNICODE_RIGHTARROW, 0); + + grub_term_setcolorstate (data->term, GRUB_TERM_COLOR_NORMAL); + grub_term_gotoxy (data->term, + (struct grub_term_coordinate) { + grub_term_cursor_x (&data->geo), y }); + + grub_term_normal_color = old_color_normal; + grub_term_highlight_color = old_color_highlight; + + grub_term_setcolorstate (data->term, GRUB_TERM_COLOR_NORMAL); + grub_free (unicode_title); +} + +static void +print_entries (grub_menu_t menu, const struct menu_viewer_data *data) +{ + grub_menu_entry_t e; + int i; + + grub_term_gotoxy (data->term, + (struct grub_term_coordinate) { + data->geo.first_entry_x + data->geo.entry_width + + data->geo.border + 1, + data->geo.first_entry_y }); + + if (data->geo.num_entries != 1) + { + if (data->first) + grub_putcode (GRUB_UNICODE_UPARROW, data->term); + else + grub_putcode (' ', data->term); + } + e = grub_menu_get_entry (menu, data->first); + + for (i = 0; i < data->geo.num_entries; i++) + { + print_entry (data->geo.first_entry_y + i, data->offset == i, + e, data); + if (e) + e = e->next; + } + + grub_term_gotoxy (data->term, + (struct grub_term_coordinate) { data->geo.first_entry_x + data->geo.entry_width + + data->geo.border + 1, + data->geo.first_entry_y + data->geo.num_entries - 1 }); + if (data->geo.num_entries == 1) + { + if (data->first && e) + grub_putcode (GRUB_UNICODE_UPDOWNARROW, data->term); + else if (data->first) + grub_putcode (GRUB_UNICODE_UPARROW, data->term); + else if (e) + grub_putcode (GRUB_UNICODE_DOWNARROW, data->term); + else + grub_putcode (' ', data->term); + } + else + { + if (e) + grub_putcode (GRUB_UNICODE_DOWNARROW, data->term); + else + grub_putcode (' ', data->term); + } + + grub_term_gotoxy (data->term, + (struct grub_term_coordinate) { grub_term_cursor_x (&data->geo), + data->geo.first_entry_y + data->offset }); +} + +/* Initialize the screen. If NESTED is non-zero, assume that this menu + is run from another menu or a command-line. If EDIT is non-zero, show + a message for the menu entry editor. */ +void +grub_menu_init_page (int nested, int edit, + struct grub_term_screen_geometry *geo, + struct grub_term_output *term) +{ + grub_uint8_t old_color_normal, old_color_highlight; + int msg_num_lines; + int bottom_message = 1; + int empty_lines = 1; + int version_msg = 1; + + geo->border = 1; + geo->first_entry_x = 1 /* margin */ + 1 /* border */; + geo->entry_width = grub_term_width (term) - 5; + + geo->first_entry_y = 2 /* two empty lines*/ + + 1 /* GNU GRUB version text */ + 1 /* top border */; + + geo->timeout_lines = 2; + + /* 3 lines for timeout message and bottom margin. 2 lines for the border. */ + geo->num_entries = grub_term_height (term) - geo->first_entry_y + - 1 /* bottom border */ + - 1 /* empty line before info message*/ + - geo->timeout_lines /* timeout */ + - 1 /* empty final line */; + msg_num_lines = print_message (nested, edit, term, 1); + if (geo->num_entries - msg_num_lines < 3 + || geo->entry_width < 10) + { + geo->num_entries += 4; + geo->first_entry_y -= 2; + empty_lines = 0; + geo->first_entry_x -= 1; + geo->entry_width += 1; + } + if (geo->num_entries - msg_num_lines < 3 + || geo->entry_width < 10) + { + geo->num_entries += 2; + geo->first_entry_y -= 1; + geo->first_entry_x -= 1; + geo->entry_width += 2; + geo->border = 0; + } + + if (geo->entry_width <= 0) + geo->entry_width = 1; + + if (geo->num_entries - msg_num_lines < 3 + && geo->timeout_lines == 2) + { + geo->timeout_lines = 1; + geo->num_entries++; + } + + if (geo->num_entries - msg_num_lines < 3) + { + geo->num_entries += 1; + geo->first_entry_y -= 1; + version_msg = 0; + } + + if (geo->num_entries - msg_num_lines >= 2) + geo->num_entries -= msg_num_lines; + else + bottom_message = 0; + + /* By default, use the same colors for the menu. */ + old_color_normal = grub_term_normal_color; + old_color_highlight = grub_term_highlight_color; + grub_color_menu_normal = grub_term_normal_color; + grub_color_menu_highlight = grub_term_highlight_color; + + /* Then give user a chance to replace them. */ + grub_parse_color_name_pair (&grub_color_menu_normal, + grub_env_get ("menu_color_normal")); + grub_parse_color_name_pair (&grub_color_menu_highlight, + grub_env_get ("menu_color_highlight")); + + if (version_msg) + grub_normal_init_page (term, empty_lines); + else + grub_term_cls (term); + + grub_term_normal_color = grub_color_menu_normal; + grub_term_highlight_color = grub_color_menu_highlight; + if (geo->border) + draw_border (term, geo); + grub_term_normal_color = old_color_normal; + grub_term_highlight_color = old_color_highlight; + geo->timeout_y = geo->first_entry_y + geo->num_entries + + geo->border + empty_lines; + if (bottom_message) + { + grub_term_gotoxy (term, + (struct grub_term_coordinate) { GRUB_TERM_MARGIN, + geo->timeout_y }); + + print_message (nested, edit, term, 0); + geo->timeout_y += msg_num_lines; + } + geo->right_margin = grub_term_width (term) + - geo->first_entry_x + - geo->entry_width - 1; +} + +static void +menu_text_print_timeout (int timeout, void *dataptr) +{ + struct menu_viewer_data *data = dataptr; + char *msg_translated = 0; + + grub_term_gotoxy (data->term, + (struct grub_term_coordinate) { 0, data->geo.timeout_y }); + + if (data->timeout_msg == TIMEOUT_TERSE + || data->timeout_msg == TIMEOUT_TERSE_NO_MARGIN) + msg_translated = grub_xasprintf (_("%ds"), timeout); + else + msg_translated = grub_xasprintf (_("The highlighted entry will be executed automatically in %ds."), timeout); + if (!msg_translated) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + return; + } + + if (data->timeout_msg == TIMEOUT_UNKNOWN) + { + data->timeout_msg = grub_print_message_indented_real (msg_translated, + 3, 1, data->term, 1) + <= data->geo.timeout_lines ? TIMEOUT_NORMAL : TIMEOUT_TERSE; + if (data->timeout_msg == TIMEOUT_TERSE) + { + grub_free (msg_translated); + msg_translated = grub_xasprintf (_("%ds"), timeout); + if (grub_term_width (data->term) < 10) + data->timeout_msg = TIMEOUT_TERSE_NO_MARGIN; + } + } + + grub_print_message_indented (msg_translated, + data->timeout_msg == TIMEOUT_TERSE_NO_MARGIN ? 0 : 3, + data->timeout_msg == TIMEOUT_TERSE_NO_MARGIN ? 0 : 1, + data->term); + grub_free (msg_translated); + + grub_term_gotoxy (data->term, + (struct grub_term_coordinate) { + grub_term_cursor_x (&data->geo), + data->geo.first_entry_y + data->offset }); + grub_term_refresh (data->term); +} + +static void +menu_text_set_chosen_entry (int entry, void *dataptr) +{ + struct menu_viewer_data *data = dataptr; + int oldoffset = data->offset; + int complete_redraw = 0; + + data->offset = entry - data->first; + if (data->offset > data->geo.num_entries - 1) + { + data->first = entry - (data->geo.num_entries - 1); + data->offset = data->geo.num_entries - 1; + complete_redraw = 1; + } + if (data->offset < 0) + { + data->offset = 0; + data->first = entry; + complete_redraw = 1; + } + if (complete_redraw) + print_entries (data->menu, data); + else + { + print_entry (data->geo.first_entry_y + oldoffset, 0, + grub_menu_get_entry (data->menu, data->first + oldoffset), + data); + print_entry (data->geo.first_entry_y + data->offset, 1, + grub_menu_get_entry (data->menu, data->first + data->offset), + data); + } + grub_term_refresh (data->term); +} + +static void +menu_text_fini (void *dataptr) +{ + struct menu_viewer_data *data = dataptr; + + grub_term_setcursor (data->term, 1); + grub_term_cls (data->term); + grub_free (data); +} + +static void +menu_text_clear_timeout (void *dataptr) +{ + struct menu_viewer_data *data = dataptr; + int i; + + for (i = 0; i < data->geo.timeout_lines;i++) + { + grub_term_gotoxy (data->term, (struct grub_term_coordinate) { + 0, data->geo.timeout_y + i }); + grub_print_spaces (data->term, grub_term_width (data->term) - 1); + } + if (data->geo.num_entries <= 5 && !data->geo.border) + { + grub_term_gotoxy (data->term, + (struct grub_term_coordinate) { + data->geo.first_entry_x + data->geo.entry_width + + data->geo.border + 1, + data->geo.first_entry_y + data->geo.num_entries - 1 + }); + grub_putcode (' ', data->term); + + data->geo.timeout_lines = 0; + data->geo.num_entries++; + print_entries (data->menu, data); + } + grub_term_gotoxy (data->term, + (struct grub_term_coordinate) { + grub_term_cursor_x (&data->geo), + data->geo.first_entry_y + data->offset }); + grub_term_refresh (data->term); +} + +grub_err_t +grub_menu_try_text (struct grub_term_output *term, + int entry, grub_menu_t menu, int nested) +{ + struct menu_viewer_data *data; + struct grub_menu_viewer *instance; + + instance = grub_zalloc (sizeof (*instance)); + if (!instance) + return grub_errno; + + data = grub_zalloc (sizeof (*data)); + if (!data) + { + grub_free (instance); + return grub_errno; + } + + data->term = term; + instance->data = data; + instance->set_chosen_entry = menu_text_set_chosen_entry; + instance->print_timeout = menu_text_print_timeout; + instance->clear_timeout = menu_text_clear_timeout; + instance->fini = menu_text_fini; + + data->menu = menu; + + data->offset = entry; + data->first = 0; + + grub_term_setcursor (data->term, 0); + grub_menu_init_page (nested, 0, &data->geo, data->term); + + if (data->offset > data->geo.num_entries - 1) + { + data->first = data->offset - (data->geo.num_entries - 1); + data->offset = data->geo.num_entries - 1; + } + + print_entries (menu, data); + grub_term_refresh (data->term); + grub_menu_register_viewer (instance); + + return GRUB_ERR_NONE; +} |