From 6e7a315eb67cb6c113cf37e1d66c4f11a51a2b3e Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 18:29:51 +0200 Subject: Adding upstream version 2.06. Signed-off-by: Daniel Baumann --- grub-core/normal/menu_entry.c | 1460 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1460 insertions(+) create mode 100644 grub-core/normal/menu_entry.c (limited to 'grub-core/normal/menu_entry.c') diff --git a/grub-core/normal/menu_entry.c b/grub-core/normal/menu_entry.c new file mode 100644 index 0000000..50eef91 --- /dev/null +++ b/grub-core/normal/menu_entry.c @@ -0,0 +1,1460 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum update_mode + { + NO_LINE, + SINGLE_LINE, + ALL_LINES + }; + +struct line +{ + /* The line buffer. */ + grub_uint32_t *buf; + /* The length of the line. */ + int len; + /* The maximum length of the line. */ + int max_len; + struct grub_term_pos **pos; +}; + +struct per_term_screen +{ + struct grub_term_output *term; + int y_line_start; + struct grub_term_screen_geometry geo; + /* Scratch variables used when updating. Having them here avoids + loads of small mallocs. */ + int orig_num; + int down; + enum update_mode mode; +}; + +struct screen +{ + /* The array of lines. */ + struct line *lines; + /* The number of lines. */ + int num_lines; + /* The current column. */ + int column; + /* The real column. */ + int real_column; + /* The current line. */ + int line; + /* The kill buffer. */ + char *killed_text; + /* The flag of a completion window. */ + int completion_shown; + + int submenu; + + struct per_term_screen *terms; + unsigned nterms; +}; + +/* Used for storing completion items temporarily. */ +static struct { + char *buf; + grub_size_t len; + grub_size_t max_len; +} completion_buffer; +static int completion_type; + +/* Initialize a line. */ +static int +init_line (struct screen *screen, struct line *linep) +{ + linep->len = 0; + linep->max_len = 80; + linep->buf = grub_calloc (linep->max_len + 1, sizeof (linep->buf[0])); + linep->pos = grub_calloc (screen->nterms, sizeof (linep->pos[0])); + if (! linep->buf || !linep->pos) + { + grub_free (linep->buf); + grub_free (linep->pos); + return 0; + } + + return 1; +} + +/* Allocate extra space if necessary. */ +static int +ensure_space (struct line *linep, int extra) +{ + if (linep->max_len < linep->len + extra) + { + grub_size_t sz0, sz1; + + if (grub_add (linep->len, extra, &sz0) || + grub_mul (sz0, 2, &sz0) || + grub_add (sz0, 1, &sz1) || + grub_mul (sz1, sizeof (linep->buf[0]), &sz1)) + return 0; + + linep->buf = grub_realloc (linep->buf, sz1); + if (! linep->buf) + return 0; + linep->max_len = sz0; + } + + return 1; +} + +/* Return the number of lines occupied by this line on the screen. */ +static int +get_logical_num_lines (struct line *linep, struct per_term_screen *term_screen) +{ + grub_size_t width = grub_getstringwidth (linep->buf, linep->buf + linep->len, + term_screen->term); + + /* Empty line still consumes space on screen */ + return width ? (width + (unsigned) term_screen->geo.entry_width - 1) / + (unsigned) term_screen->geo.entry_width + : 1; +} + +static void +advance_to (struct screen *screen, int c) +{ + if (c > screen->lines[screen->line].len) + c = screen->lines[screen->line].len; + + screen->column = grub_unicode_get_comb_end (screen->lines[screen->line].buf + + screen->lines[screen->line].len, + screen->lines[screen->line].buf + + c) + - screen->lines[screen->line].buf; +} + +/* Print an empty line. */ +static void +print_empty_line (int y, struct per_term_screen *term_screen) +{ + int i; + + grub_term_gotoxy (term_screen->term, + (struct grub_term_coordinate) { term_screen->geo.first_entry_x, + y + term_screen->geo.first_entry_y }); + + for (i = 0; i < term_screen->geo.entry_width + 1; i++) + grub_putcode (' ', term_screen->term); +} + +static void +print_updown (int upflag, int downflag, struct per_term_screen *term_screen) +{ + grub_term_gotoxy (term_screen->term, + (struct grub_term_coordinate) { term_screen->geo.first_entry_x + + term_screen->geo.entry_width + 1 + + term_screen->geo.border, + term_screen->geo.first_entry_y }); + + if (upflag && downflag) + grub_putcode (GRUB_UNICODE_UPDOWNARROW, term_screen->term); + else if (upflag) + grub_putcode (GRUB_UNICODE_UPARROW, term_screen->term); + else if (downflag) + grub_putcode (GRUB_UNICODE_DOWNARROW, term_screen->term); + else + grub_putcode (' ', term_screen->term); +} + +/* Print an up arrow. */ +static void +print_up (int flag, struct per_term_screen *term_screen) +{ + grub_term_gotoxy (term_screen->term, + (struct grub_term_coordinate) { term_screen->geo.first_entry_x + + term_screen->geo.entry_width + 1 + + term_screen->geo.border, + term_screen->geo.first_entry_y }); + + if (flag) + grub_putcode (GRUB_UNICODE_UPARROW, term_screen->term); + else + grub_putcode (' ', term_screen->term); +} + +/* Print a down arrow. */ +static void +print_down (int flag, struct per_term_screen *term_screen) +{ + grub_term_gotoxy (term_screen->term, + (struct grub_term_coordinate) { term_screen->geo.first_entry_x + + term_screen->geo.entry_width + 1 + + term_screen->geo.border, + term_screen->geo.first_entry_y + + term_screen->geo.num_entries - 1 }); + + if (flag) + grub_putcode (GRUB_UNICODE_DOWNARROW, term_screen->term); + else + grub_putcode (' ', term_screen->term); +} + +/* Draw the lines of the screen SCREEN. */ +static void +update_screen (struct screen *screen, struct per_term_screen *term_screen, + int region_start, int region_column __attribute__ ((unused)), + int up, int down, enum update_mode mode) +{ + int up_flag = 0; + int down_flag = 0; + int y; + int i; + struct line *linep; + + y = term_screen->y_line_start; + linep = screen->lines; + + for (i = 0; i < screen->line; i++, linep++) + y += get_logical_num_lines (linep, term_screen); + linep = screen->lines + screen->line; + grub_size_t t = grub_getstringwidth (linep->buf, linep->buf + screen->column, + term_screen->term); + y += t / (unsigned) term_screen->geo.entry_width; + if (t % (unsigned) term_screen->geo.entry_width == 0 + && t != 0 && screen->column == linep->len) + y--; + /* Check if scrolling is necessary. */ + if (y < 0 || y >= term_screen->geo.num_entries) + { + int delta; + if (y < 0) + delta = -y; + else + delta = term_screen->geo.num_entries - 1 - y; + term_screen->y_line_start += delta; + + region_start = 0; + up = 1; + down = 1; + mode = ALL_LINES; + } + + grub_term_setcursor (term_screen->term, 0); + + if (mode != NO_LINE) + { + /* Draw lines. This code is tricky, because this must calculate logical + positions. */ + y = term_screen->y_line_start; + i = 0; + linep = screen->lines; + while (1) + { + int add; + add = get_logical_num_lines (linep, term_screen); + if (y + add > 0) + break; + i++; + linep++; + y += add; + } + + if (y < 0 || i > 0) + up_flag = 1; + + do + { + struct grub_term_pos **pos; + + if (linep >= screen->lines + screen->num_lines) + break; + + pos = linep->pos + (term_screen - screen->terms); + + if (!*pos) + *pos = grub_calloc (linep->len + 1, sizeof (**pos)); + + if (i == region_start || linep == screen->lines + screen->line + || (i > region_start && mode == ALL_LINES)) + { + grub_term_gotoxy (term_screen->term, + (struct grub_term_coordinate) { term_screen->geo.first_entry_x, + (y < 0 ? 0 : y) + + term_screen->geo.first_entry_y }); + + grub_print_ucs4_menu (linep->buf, + linep->buf + linep->len, + term_screen->geo.first_entry_x, + term_screen->geo.right_margin, + term_screen->term, + (y < 0) ? -y : 0, + term_screen->geo.num_entries + - ((y > 0) ? y : 0), '\\', + *pos); + } + y += get_logical_num_lines (linep, term_screen); + if (y >= term_screen->geo.num_entries) + { + if (i + 1 < screen->num_lines) + down_flag = 1; + } + + linep++; + i++; + + if (mode == ALL_LINES && i == screen->num_lines) + for (; y < term_screen->geo.num_entries; y++) + print_empty_line (y, term_screen); + } + while (y < term_screen->geo.num_entries); + + /* Draw up and down arrows. */ + + if (term_screen->geo.num_entries == 1) + { + if (up || down) + print_updown (up_flag, down_flag, term_screen); + } + else + { + if (up) + print_up (up_flag, term_screen); + if (down) + print_down (down_flag, term_screen); + } + } + + /* Place the cursor. */ + if (screen->lines[screen->line].pos[term_screen - screen->terms]) + { + const struct grub_term_pos *cpos; + for (cpos = &(screen->lines[screen->line].pos[term_screen - screen->terms])[screen->column]; + cpos >= &(screen->lines[screen->line].pos[term_screen - screen->terms])[0]; + cpos--) + if (cpos->valid) + break; + y = term_screen->y_line_start; + for (i = 0; i < screen->line; i++) + y += get_logical_num_lines (screen->lines + i, term_screen); + if (cpos >= &(screen->lines[screen->line].pos[term_screen - screen->terms])[0]) + grub_term_gotoxy (term_screen->term, + (struct grub_term_coordinate) { cpos->x + term_screen->geo.first_entry_x, + cpos->y + y + + term_screen->geo.first_entry_y }); + else + grub_term_gotoxy (term_screen->term, + (struct grub_term_coordinate) { term_screen->geo.first_entry_x, + y + term_screen->geo.first_entry_y }); + + } + + grub_term_setcursor (term_screen->term, 1); + + grub_term_refresh (term_screen->term); +} + +static void +update_screen_all (struct screen *screen, + int region_start, int region_column, + int up, int down, enum update_mode mode) +{ + unsigned i; + for (i = 0; i < screen->nterms; i++) + update_screen (screen, &screen->terms[i], region_start, region_column, + up, down, mode); +} + +static int +insert_string (struct screen *screen, const char *s, int update) +{ + int region_start = screen->num_lines; + int region_column = 0; + unsigned i; + + for (i = 0; i < screen->nterms; i++) + { + screen->terms[i].down = 0; + screen->terms[i].mode = NO_LINE; + } + + while (*s) + { + if (*s == '\n') + { + /* LF is special because it creates a new line. */ + struct line *current_linep; + struct line *next_linep; + int size; + + /* Make a new line. */ + screen->num_lines++; + screen->lines = grub_realloc (screen->lines, + screen->num_lines + * sizeof (screen->lines[0])); + if (! screen->lines) + return 0; + + /* Shift down if not appending after the last line. */ + if (screen->line < screen->num_lines - 2) + grub_memmove (screen->lines + screen->line + 2, + screen->lines + screen->line + 1, + ((screen->num_lines - screen->line - 2) + * sizeof (struct line))); + + if (! init_line (screen, screen->lines + screen->line + 1)) + return 0; + + /* Fold the line. */ + current_linep = screen->lines + screen->line; + next_linep = current_linep + 1; + size = current_linep->len - screen->column; + + if (! ensure_space (next_linep, size)) + return 0; + + grub_memmove (next_linep->buf, + current_linep->buf + screen->column, + size * sizeof (next_linep->buf[0])); + current_linep->len = screen->column; + for (i = 0; i < screen->nterms; i++) + { + grub_free (current_linep->pos[i]); + current_linep->pos[i] = 0; + } + next_linep->len = size; + + /* Update a dirty region. */ + if (region_start > screen->line) + { + region_start = screen->line; + region_column = screen->column; + } + + for (i = 0; i < screen->nterms; i++) + { + screen->terms[i].mode = ALL_LINES; + screen->terms[i].down = 1; /* XXX not optimal. */ + } + + /* Move the cursor. */ + screen->column = screen->real_column = 0; + screen->line++; + s++; + } + else + { + /* All but LF. */ + const char *p; + struct line *current_linep; + int size; + grub_uint32_t *unicode_msg; + + /* Find a string delimited by LF. */ + p = grub_strchr (s, '\n'); + if (! p) + p = s + grub_strlen (s); + + /* Insert the string. */ + current_linep = screen->lines + screen->line; + unicode_msg = grub_calloc (p - s, sizeof (grub_uint32_t)); + + if (!unicode_msg) + return 0; + + size = grub_utf8_to_ucs4 (unicode_msg, (p - s), + (grub_uint8_t *) s, (p - s), 0); + + if (! ensure_space (current_linep, size)) + { + grub_free (unicode_msg); + return 0; + } + + grub_memmove (current_linep->buf + screen->column + size, + current_linep->buf + screen->column, + (current_linep->len - screen->column) + * sizeof (current_linep->buf[0])); + grub_memmove (current_linep->buf + screen->column, + unicode_msg, + size * sizeof (current_linep->buf[0])); + grub_free (unicode_msg); + + for (i = 0; i < screen->nterms; i++) + { + grub_free (current_linep->pos[i]); + current_linep->pos[i] = 0; + } + + for (i = 0; i < screen->nterms; i++) + screen->terms[i].orig_num = get_logical_num_lines (current_linep, + &screen->terms[i]); + current_linep->len += size; + + /* Update the dirty region. */ + if (region_start > screen->line) + { + region_start = screen->line; + region_column = screen->column; + } + + for (i = 0; i < screen->nterms; i++) + { + int new_num = get_logical_num_lines (current_linep, + &screen->terms[i]); + if (screen->terms[i].orig_num != new_num) + { + screen->terms[i].mode = ALL_LINES; + screen->terms[i].down = 1; /* XXX not optimal. */ + } + else if (screen->terms[i].mode != ALL_LINES) + screen->terms[i].mode = SINGLE_LINE; + } + + /* Move the cursor. */ + advance_to (screen, screen->column + size); + + screen->real_column = screen->column; + s = p; + } + } + + if (update) + for (i = 0; i < screen->nterms; i++) + update_screen (screen, &screen->terms[i], + region_start, region_column, 0, screen->terms[i].down, + screen->terms[i].mode); + + return 1; +} + +/* Release the resource allocated for SCREEN. */ +static void +destroy_screen (struct screen *screen) +{ + int i; + + if (screen->lines) + for (i = 0; i < screen->num_lines; i++) + { + struct line *linep = screen->lines + i; + + if (linep) + { + unsigned j; + if (linep->pos) + for (j = 0; j < screen->nterms; j++) + grub_free (linep->pos[j]); + + grub_free (linep->buf); + grub_free (linep->pos); + } + } + + grub_free (screen->killed_text); + grub_free (screen->lines); + grub_free (screen->terms); + grub_free (screen); +} + +/* Make a new screen. */ +static struct screen * +make_screen (grub_menu_entry_t entry) +{ + struct screen *screen; + unsigned i; + + /* Initialize the screen. */ + screen = grub_zalloc (sizeof (*screen)); + if (! screen) + return 0; + + screen->submenu = entry->submenu; + + screen->num_lines = 1; + screen->lines = grub_malloc (sizeof (struct line)); + if (! screen->lines) + goto fail; + + /* Initialize the first line which must be always present. */ + if (! init_line (screen, screen->lines)) + goto fail; + + insert_string (screen, (char *) entry->sourcecode, 0); + + /* Reset the cursor position. */ + screen->column = 0; + screen->real_column = 0; + screen->line = 0; + for (i = 0; i < screen->nterms; i++) + { + screen->terms[i].y_line_start = 0; + } + + return screen; + + fail: + destroy_screen (screen); + return 0; +} + +static int +forward_char (struct screen *screen, int update) +{ + struct line *linep; + + linep = screen->lines + screen->line; + if (screen->column < linep->len) + { + screen->column = grub_unicode_get_comb_end (screen->lines[screen->line].buf + + screen->lines[screen->line].len, + screen->lines[screen->line].buf + + screen->column + 1) + - screen->lines[screen->line].buf; + } + else if (screen->num_lines > screen->line + 1) + { + screen->column = 0; + screen->line++; + } + + screen->real_column = screen->column; + + if (update) + update_screen_all (screen, screen->num_lines, 0, 0, 0, NO_LINE); + return 1; +} + +static int +backward_char (struct screen *screen, int update) +{ + if (screen->column > 0) + { + struct grub_unicode_glyph glyph; + struct line *linep; + + linep = screen->lines + screen->line; + + screen->column--; + screen->column = grub_unicode_get_comb_start (linep->buf, + linep->buf + screen->column) + - linep->buf; + + grub_unicode_aglomerate_comb (screen->lines[screen->line].buf + screen->column, + screen->lines[screen->line].len - screen->column, + &glyph); + screen->column = grub_unicode_get_comb_start (linep->buf, + linep->buf + screen->column) + - linep->buf; + + grub_unicode_destroy_glyph (&glyph); + } + else if (screen->line > 0) + { + screen->line--; + screen->column = screen->lines[screen->line].len; + } + + screen->real_column = screen->column; + + if (update) + update_screen_all (screen, screen->num_lines, 0, 0, 0, NO_LINE); + + return 1; +} + +static int +previous_line (struct screen *screen, int update) +{ + if (screen->line > 0) + { + struct line *linep; + int col; + + screen->line--; + + linep = screen->lines + screen->line; + if (linep->len < screen->real_column) + col = linep->len; + else + col = screen->real_column; + + screen->column = 0; + advance_to (screen, col); + } + else + { + screen->column = 0; + } + + if (update) + update_screen_all (screen, screen->num_lines, 0, 0, 0, NO_LINE); + + return 1; +} + +static int +next_line (struct screen *screen, int update) +{ + if (screen->line < screen->num_lines - 1) + { + struct line *linep; + int c; + + /* How many physical lines from the current position + to the last physical line? */ + linep = screen->lines + screen->line; + + screen->line++; + if ((linep + 1)->len < screen->real_column) + c = (linep + 1)->len; + else + c = screen->real_column; + screen->column = 0; + + advance_to (screen, c); + } + else + advance_to (screen, screen->lines[screen->line].len); + + if (update) + update_screen_all (screen, screen->num_lines, 0, 0, 0, NO_LINE); + + return 1; +} + +static int +beginning_of_line (struct screen *screen, int update) +{ + screen->column = screen->real_column = 0; + + if (update) + update_screen_all (screen, screen->num_lines, 0, 0, 0, NO_LINE); + + return 1; +} + +static int +end_of_line (struct screen *screen, int update) +{ + advance_to (screen, screen->lines[screen->line].len); + + if (update) + update_screen_all (screen, screen->num_lines, 0, 0, 0, NO_LINE); + + return 1; +} + +static int +delete_char (struct screen *screen, int update) +{ + struct line *linep; + int start = screen->num_lines; + int column = 0; + + linep = screen->lines + screen->line; + if (linep->len > screen->column) + { + unsigned i; + + for (i = 0; i < screen->nterms; i++) + screen->terms[i].orig_num = get_logical_num_lines (linep, &screen->terms[i]); + + grub_memmove (linep->buf + screen->column, + linep->buf + screen->column + 1, + (linep->len - screen->column - 1) + * sizeof (linep->buf[0])); + linep->len--; + + for (i = 0; i < screen->nterms; i++) + { + grub_free (linep->pos[i]); + linep->pos[i] = 0; + } + start = screen->line; + column = screen->column; + + screen->real_column = screen->column; + + if (update) + { + for (i = 0; i < screen->nterms; i++) + { + int new_num; + new_num = get_logical_num_lines (linep, &screen->terms[i]); + if (screen->terms[i].orig_num != new_num) + update_screen (screen, &screen->terms[i], + start, column, 0, 0, ALL_LINES); + else + update_screen (screen, &screen->terms[i], + start, column, 0, 0, SINGLE_LINE); + } + } + } + else if (screen->num_lines > screen->line + 1) + { + struct line *next_linep; + unsigned i; + + next_linep = linep + 1; + if (! ensure_space (linep, next_linep->len)) + return 0; + + grub_memmove (linep->buf + linep->len, next_linep->buf, + next_linep->len * sizeof (linep->buf[0])); + linep->len += next_linep->len; + for (i = 0; i < screen->nterms; i++) + { + grub_free (linep->pos[i]); + linep->pos[i] = 0; + } + + grub_free (next_linep->buf); + grub_memmove (next_linep, + next_linep + 1, + (screen->num_lines - screen->line - 2) + * sizeof (struct line)); + screen->num_lines--; + + start = screen->line; + column = screen->column; + + screen->real_column = screen->column; + if (update) + update_screen_all (screen, start, column, 0, 1, ALL_LINES); + } + + return 1; +} + +static int +backward_delete_char (struct screen *screen, int update) +{ + int saved_column; + int saved_line; + + saved_column = screen->column; + saved_line = screen->line; + + if (! backward_char (screen, 0)) + return 0; + + if (saved_column != screen->column || saved_line != screen->line) + if (! delete_char (screen, update)) + return 0; + + return 1; +} + +static int +kill_line (struct screen *screen, int continuous, int update) +{ + struct line *linep; + char *p; + int size; + int offset; + + p = screen->killed_text; + if (! continuous && p) + p[0] = '\0'; + + linep = screen->lines + screen->line; + size = linep->len - screen->column; + + if (p) + offset = grub_strlen (p); + else + offset = 0; + + if (size > 0) + { + unsigned i; + + p = grub_realloc (p, offset + size + 1); + if (! p) + return 0; + + grub_memmove (p + offset, linep->buf + screen->column, size); + p[offset + size] = '\0'; + + screen->killed_text = p; + + for (i = 0; i < screen->nterms; i++) + screen->terms[i].orig_num = get_logical_num_lines (linep, &screen->terms[i]); + linep->len = screen->column; + + if (update) + { + for (i = 0; i < screen->nterms; i++) + { + int new_num; + new_num = get_logical_num_lines (linep, &screen->terms[i]); + if (screen->terms[i].orig_num != new_num) + update_screen (screen, &screen->terms[i], + screen->line, screen->column, 0, 1, ALL_LINES); + else + update_screen (screen, &screen->terms[i], + screen->line, screen->column, 0, 0, SINGLE_LINE); + } + } + } + else if (screen->line + 1 < screen->num_lines) + { + p = grub_realloc (p, offset + 1 + 1); + if (! p) + return 0; + + p[offset] = '\n'; + p[offset + 1] = '\0'; + + screen->killed_text = p; + + return delete_char (screen, update); + } + + return 1; +} + +static int +yank (struct screen *screen, int update) +{ + if (screen->killed_text) + return insert_string (screen, screen->killed_text, update); + + return 1; +} + +static int +open_line (struct screen *screen, int update) +{ + if (! insert_string (screen, "\n", 0)) + return 0; + + if (! backward_char (screen, 0)) + return 0; + + if (update) + update_screen_all (screen, screen->line, screen->column, 0, 1, ALL_LINES); + + return 1; +} + +/* A completion hook to print items. */ +static void +store_completion (const char *item, grub_completion_type_t type, + int count __attribute__ ((unused))) +{ + char *p; + + completion_type = type; + + /* Make sure that the completion buffer has enough room. */ + if (completion_buffer.max_len < (completion_buffer.len + + (int) grub_strlen (item) + 1 + 1)) + { + grub_size_t new_len; + + new_len = completion_buffer.len + grub_strlen (item) + 80; + p = grub_realloc (completion_buffer.buf, new_len); + if (! p) + { + /* Possibly not fatal. */ + grub_errno = GRUB_ERR_NONE; + return; + } + p[completion_buffer.len] = 0; + completion_buffer.buf = p; + completion_buffer.max_len = new_len; + } + + p = completion_buffer.buf + completion_buffer.len; + if (completion_buffer.len != 0) + { + *p++ = ' '; + completion_buffer.len++; + } + grub_strcpy (p, item); + completion_buffer.len += grub_strlen (item); +} + +static int +complete (struct screen *screen, int continuous, int update) +{ + struct line *linep; + int restore; + char *insert; + static int count = -1; + unsigned i; + grub_uint32_t *ucs4; + grub_size_t buflen; + grub_ssize_t ucs4len; + char *u8; + + if (continuous) + count++; + else + count = 0; + + completion_buffer.buf = 0; + completion_buffer.len = 0; + completion_buffer.max_len = 0; + + linep = screen->lines + screen->line; + u8 = grub_ucs4_to_utf8_alloc (linep->buf, screen->column); + if (!u8) + return 1; + + insert = grub_normal_do_completion (u8, &restore, store_completion); + + if (completion_buffer.buf) + { + buflen = grub_strlen (completion_buffer.buf); + ucs4 = grub_calloc (buflen + 1, sizeof (grub_uint32_t)); + + if (!ucs4) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + return 1; + } + + ucs4len = grub_utf8_to_ucs4 (ucs4, buflen, + (grub_uint8_t *) completion_buffer.buf, + buflen, 0); + ucs4[ucs4len] = 0; + + if (restore) + for (i = 0; i < screen->nterms; i++) + { + unsigned width = grub_term_width (screen->terms[i].term); + if (width > 2) + width -= 2; + if (width > 15) + width -= 6; + unsigned num_sections = ((completion_buffer.len + + width - 1) + / width); + grub_uint32_t *endp; + struct grub_term_coordinate pos; + grub_uint32_t *p = ucs4; + + pos = grub_term_getxy (screen->terms[i].term); + + screen->completion_shown = 1; + + grub_term_gotoxy (screen->terms[i].term, + (struct grub_term_coordinate) { 0, + screen->terms[i].geo.timeout_y }); + if (screen->terms[i].geo.timeout_lines >= 2) + { + grub_puts_terminal (" ", screen->terms[i].term); + switch (completion_type) + { + case GRUB_COMPLETION_TYPE_COMMAND: + grub_puts_terminal (_("Possible commands are:"), + screen->terms[i].term); + break; + case GRUB_COMPLETION_TYPE_DEVICE: + grub_puts_terminal (_("Possible devices are:"), + screen->terms[i].term); + break; + case GRUB_COMPLETION_TYPE_FILE: + grub_puts_terminal (_("Possible files are:"), + screen->terms[i].term); + break; + case GRUB_COMPLETION_TYPE_PARTITION: + grub_puts_terminal (_("Possible partitions are:"), + screen->terms[i].term); + break; + case GRUB_COMPLETION_TYPE_ARGUMENT: + grub_puts_terminal (_("Possible arguments are:"), + screen->terms[i].term); + break; + default: + grub_puts_terminal (_("Possible things are:"), + screen->terms[i].term); + break; + } + + grub_puts_terminal ("\n ", screen->terms[i].term); + } + + p += ((unsigned) count % num_sections) * width; + endp = p + width; + + if (p != ucs4) + grub_putcode (GRUB_UNICODE_LEFTARROW, screen->terms[i].term); + else + grub_putcode (' ', screen->terms[i].term); + + grub_print_ucs4 (p, ucs4 + ucs4len < endp ? ucs4 + ucs4len : endp, + 0, 0, screen->terms[i].term); + + if (ucs4 + ucs4len > endp) + grub_putcode (GRUB_UNICODE_RIGHTARROW, screen->terms[i].term); + grub_term_gotoxy (screen->terms[i].term, pos); + } + } + + if (insert) + { + insert_string (screen, insert, update); + count = -1; + grub_free (insert); + } + else if (update) + grub_refresh (); + + grub_free (completion_buffer.buf); + return 1; +} + +/* Clear displayed completions. */ +static void +clear_completions (struct per_term_screen *term_screen) +{ + struct grub_term_coordinate pos; + unsigned j; + int i; + + pos = grub_term_getxy (term_screen->term); + grub_term_gotoxy (term_screen->term, + (struct grub_term_coordinate) { 0, + term_screen->geo.timeout_y }); + + for (i = 0; i < term_screen->geo.timeout_lines; i++) + { + for (j = 0; j < grub_term_width (term_screen->term) - 1; j++) + grub_putcode (' ', term_screen->term); + if (i + 1 < term_screen->geo.timeout_lines) + grub_putcode ('\n', term_screen->term); + } + + grub_term_gotoxy (term_screen->term, pos); + grub_term_refresh (term_screen->term); +} + +static void +clear_completions_all (struct screen *screen) +{ + unsigned i; + + for (i = 0; i < screen->nterms; i++) + clear_completions (&screen->terms[i]); +} + +/* Execute the command list in the screen SCREEN. */ +static int +run (struct screen *screen) +{ + char *script; + int errs_before; + grub_menu_t menu = NULL; + char *dummy[1] = { NULL }; + + grub_cls (); + grub_printf (" "); + grub_printf_ (N_("Booting a command list")); + grub_printf ("\n\n"); + + errs_before = grub_err_printed_errors; + + if (screen->submenu) + { + grub_env_context_open (); + menu = grub_zalloc (sizeof (*menu)); + if (! menu) + return 0; + grub_env_set_menu (menu); + } + + /* Execute the script, line for line. */ + { + int i; + grub_size_t size = 0, tot_size = 0; + + for (i = 0; i < screen->num_lines; i++) + tot_size += grub_get_num_of_utf8_bytes (screen->lines[i].buf, + screen->lines[i].len) + 1; + + script = grub_malloc (tot_size + 1); + if (! script) + return 0; + + for (i = 0; i < screen->num_lines; i++) + { + size += grub_ucs4_to_utf8 (screen->lines[i].buf, screen->lines[i].len, + (grub_uint8_t *) script + size, + tot_size - size); + script[size++] = '\n'; + } + script[size] = '\0'; + } + grub_script_execute_new_scope (script, 0, dummy); + grub_free (script); + + if (errs_before != grub_err_printed_errors) + grub_wait_after_message (); + + if (grub_errno == GRUB_ERR_NONE && grub_loader_is_loaded ()) + /* Implicit execution of boot, only if something is loaded. */ + grub_command_execute ("boot", 0, 0); + + if (screen->submenu) + { + if (menu && menu->size) + { + grub_show_menu (menu, 1, 0); + grub_normal_free_menu (menu); + } + grub_env_context_close (); + } + + if (grub_errno != GRUB_ERR_NONE) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + grub_wait_after_message (); + } + + return 1; +} + +/* Edit a menu entry with an Emacs-like interface. */ +void +grub_menu_entry_run (grub_menu_entry_t entry) +{ + struct screen *screen; + int prev_c; + grub_err_t err = GRUB_ERR_NONE; + unsigned i; + grub_term_output_t term; + + err = grub_auth_check_authentication (NULL); + + if (err) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + return; + } + + screen = make_screen (entry); + if (! screen) + return; + + screen->terms = NULL; + + refresh: + grub_free (screen->terms); + screen->nterms = 0; + FOR_ACTIVE_TERM_OUTPUTS(term) + screen->nterms++; + + for (i = 0; i < (unsigned) screen->num_lines; i++) + { + grub_free (screen->lines[i].pos); + screen->lines[i].pos = grub_calloc (screen->nterms, sizeof (screen->lines[i].pos[0])); + if (! screen->lines[i].pos) + { + grub_print_error (); + destroy_screen (screen); + grub_errno = GRUB_ERR_NONE; + return; + } + } + + screen->terms = grub_calloc (screen->nterms, sizeof (screen->terms[0])); + if (!screen->terms) + { + grub_print_error (); + destroy_screen (screen); + grub_errno = GRUB_ERR_NONE; + return; + } + i = 0; + FOR_ACTIVE_TERM_OUTPUTS(term) + { + screen->terms[i].term = term; + screen->terms[i].y_line_start = 0; + i++; + } + /* Draw the screen. */ + for (i = 0; i < screen->nterms; i++) + grub_menu_init_page (0, 1, &screen->terms[i].geo, + screen->terms[i].term); + update_screen_all (screen, 0, 0, 1, 1, ALL_LINES); + for (i = 0; i < screen->nterms; i++) + grub_term_setcursor (screen->terms[i].term, 1); + prev_c = '\0'; + + while (1) + { + int c = grub_getkey (); + + if (screen->completion_shown) + { + clear_completions_all (screen); + screen->completion_shown = 0; + } + + if (grub_normal_exit_level) + { + destroy_screen (screen); + return; + } + + switch (c) + { + case GRUB_TERM_KEY_UP: + case GRUB_TERM_CTRL | 'p': + if (! previous_line (screen, 1)) + goto fail; + break; + + case GRUB_TERM_CTRL | 'n': + case GRUB_TERM_KEY_DOWN: + if (! next_line (screen, 1)) + goto fail; + break; + + case GRUB_TERM_CTRL | 'f': + case GRUB_TERM_KEY_RIGHT: + if (! forward_char (screen, 1)) + goto fail; + break; + + case GRUB_TERM_CTRL | 'b': + case GRUB_TERM_KEY_LEFT: + if (! backward_char (screen, 1)) + goto fail; + break; + + case GRUB_TERM_CTRL | 'a': + case GRUB_TERM_KEY_HOME: + if (! beginning_of_line (screen, 1)) + goto fail; + break; + + case GRUB_TERM_CTRL | 'e': + case GRUB_TERM_KEY_END: + if (! end_of_line (screen, 1)) + goto fail; + break; + + case GRUB_TERM_CTRL | 'i': + case '\t': + if (! complete (screen, prev_c == c, 1)) + goto fail; + break; + + case GRUB_TERM_CTRL | 'd': + case GRUB_TERM_KEY_DC: + if (! delete_char (screen, 1)) + goto fail; + break; + + case GRUB_TERM_CTRL | 'h': + case '\b': + if (! backward_delete_char (screen, 1)) + goto fail; + break; + + case GRUB_TERM_CTRL | 'k': + if (! kill_line (screen, prev_c == c, 1)) + goto fail; + break; + + case GRUB_TERM_CTRL | 'u': + /* FIXME: What behavior is good for this key? */ + break; + + case GRUB_TERM_CTRL | 'y': + if (! yank (screen, 1)) + goto fail; + break; + + case GRUB_TERM_CTRL | 'l': + /* FIXME: centering. */ + goto refresh; + + case GRUB_TERM_CTRL | 'o': + if (! open_line (screen, 1)) + goto fail; + break; + + case '\n': + case '\r': + if (! insert_string (screen, "\n", 1)) + goto fail; + break; + + case GRUB_TERM_ESC: + destroy_screen (screen); + return; + + case GRUB_TERM_CTRL | 'c': + case GRUB_TERM_KEY_F2: + grub_cmdline_run (1, 0); + goto refresh; + + case GRUB_TERM_CTRL | 'x': + case GRUB_TERM_KEY_F10: + run (screen); + goto refresh; + + case GRUB_TERM_CTRL | 'r': + case GRUB_TERM_CTRL | 's': + case GRUB_TERM_CTRL | 't': + /* FIXME */ + break; + + default: + if (grub_isprint (c)) + { + char buf[2]; + + buf[0] = c; + buf[1] = '\0'; + if (! insert_string (screen, buf, 1)) + goto fail; + } + break; + } + + prev_c = c; + } + + fail: + destroy_screen (screen); + + grub_cls (); + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + grub_xputs ("\n"); + grub_printf_ (N_("Press any key to continue...")); + (void) grub_getkey (); +} -- cgit v1.2.3