| /* |
| * 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 <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <grub/normal.h> |
| #include <grub/term.h> |
| #include <grub/misc.h> |
| #include <grub/mm.h> |
| #include <grub/loader.h> |
| #include <grub/command.h> |
| #include <grub/parser.h> |
| #include <grub/script_sh.h> |
| #include <grub/auth.h> |
| #include <grub/i18n.h> |
| #include <grub/charset.h> |
| |
| 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_malloc ((linep->max_len + 1) * sizeof (linep->buf[0])); |
| linep->pos = grub_zalloc (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) |
| { |
| linep->max_len = 2 * (linep->len + extra); |
| linep->buf = grub_realloc (linep->buf, (linep->max_len + 1) * sizeof (linep->buf[0])); |
| if (! linep->buf) |
| return 0; |
| } |
| |
| 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_zalloc ((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_malloc ((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_malloc (sizeof (grub_uint32_t) * (buflen + 1)); |
| |
| 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_zalloc (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_zalloc (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 '\e': |
| 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 (); |
| } |