| /* theme_loader.c - Theme file loader for gfxmenu. */ |
| /* |
| * GRUB -- GRand Unified Bootloader |
| * Copyright (C) 2008 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/types.h> |
| #include <grub/file.h> |
| #include <grub/misc.h> |
| #include <grub/mm.h> |
| #include <grub/err.h> |
| #include <grub/dl.h> |
| #include <grub/video.h> |
| #include <grub/gui_string_util.h> |
| #include <grub/bitmap.h> |
| #include <grub/bitmap_scale.h> |
| #include <grub/gfxwidgets.h> |
| #include <grub/gfxmenu_view.h> |
| #include <grub/gui.h> |
| #include <grub/color.h> |
| |
| static grub_err_t |
| parse_proportional_spec (const char *value, signed *abs, grub_fixed_signed_t *prop); |
| |
| /* Construct a new box widget using ABSPATTERN to find the pixmap files for |
| it, storing the new box instance at *BOXPTR. |
| PATTERN should be of the form: "(hd0,0)/somewhere/style*.png". |
| The '*' then gets substituted with the various pixmap names that the |
| box uses. */ |
| static grub_err_t |
| recreate_box_absolute (grub_gfxmenu_box_t *boxptr, const char *abspattern) |
| { |
| char *prefix; |
| char *suffix; |
| char *star; |
| grub_gfxmenu_box_t box; |
| |
| star = grub_strchr (abspattern, '*'); |
| if (! star) |
| return grub_error (GRUB_ERR_BAD_ARGUMENT, |
| "missing `*' in box pixmap pattern `%s'", abspattern); |
| |
| /* Prefix: Get the part before the '*'. */ |
| prefix = grub_malloc (star - abspattern + 1); |
| if (! prefix) |
| return grub_errno; |
| |
| grub_memcpy (prefix, abspattern, star - abspattern); |
| prefix[star - abspattern] = '\0'; |
| |
| /* Suffix: Everything after the '*' is the suffix. */ |
| suffix = star + 1; |
| |
| box = grub_gfxmenu_create_box (prefix, suffix); |
| grub_free (prefix); |
| if (! box) |
| return grub_errno; |
| |
| if (*boxptr) |
| (*boxptr)->destroy (*boxptr); |
| *boxptr = box; |
| return grub_errno; |
| } |
| |
| |
| /* Construct a new box widget using PATTERN to find the pixmap files for it, |
| storing the new widget at *BOXPTR. PATTERN should be of the form: |
| "somewhere/style*.png". The '*' then gets substituted with the various |
| pixmap names that the widget uses. |
| |
| Important! The value of *BOXPTR must be initialized! It must either |
| (1) Be 0 (a NULL pointer), or |
| (2) Be a pointer to a valid 'grub_gfxmenu_box_t' instance. |
| In this case, the previous instance is destroyed. */ |
| grub_err_t |
| grub_gui_recreate_box (grub_gfxmenu_box_t *boxptr, |
| const char *pattern, const char *theme_dir) |
| { |
| char *abspattern; |
| |
| /* Check arguments. */ |
| if (! pattern) |
| { |
| /* If no pixmap pattern is given, then just create an empty box. */ |
| if (*boxptr) |
| (*boxptr)->destroy (*boxptr); |
| *boxptr = grub_gfxmenu_create_box (0, 0); |
| return grub_errno; |
| } |
| |
| if (! theme_dir) |
| return grub_error (GRUB_ERR_BAD_ARGUMENT, |
| "styled box missing theme directory"); |
| |
| /* Resolve to an absolute path. */ |
| abspattern = grub_resolve_relative_path (theme_dir, pattern); |
| if (! abspattern) |
| return grub_errno; |
| |
| /* Create the box. */ |
| recreate_box_absolute (boxptr, abspattern); |
| grub_free (abspattern); |
| return grub_errno; |
| } |
| |
| static grub_err_t |
| theme_get_unsigned_int_from_proportional (const char *value, |
| unsigned absolute_value, |
| unsigned int *parsed_value) |
| { |
| grub_err_t err; |
| grub_fixed_signed_t frac; |
| signed pixels; |
| err = parse_proportional_spec (value, &pixels, &frac); |
| if (err != GRUB_ERR_NONE) |
| return err; |
| int result = grub_fixed_sfs_multiply (absolute_value, frac) + pixels; |
| if (result < 0) |
| result = 0; |
| *parsed_value = result; |
| return GRUB_ERR_NONE; |
| } |
| |
| /* Set the specified property NAME on the view to the given string VALUE. |
| The caller is responsible for the lifetimes of NAME and VALUE. */ |
| static grub_err_t |
| theme_set_string (grub_gfxmenu_view_t view, |
| const char *name, |
| const char *value, |
| const char *theme_dir, |
| const char *filename, |
| int line_num, |
| int col_num) |
| { |
| if (! grub_strcmp ("title-font", name)) |
| view->title_font = grub_font_get (value); |
| else if (! grub_strcmp ("message-font", name)) |
| view->message_font = grub_font_get (value); |
| else if (! grub_strcmp ("terminal-font", name)) |
| { |
| grub_free (view->terminal_font_name); |
| view->terminal_font_name = grub_strdup (value); |
| if (! view->terminal_font_name) |
| return grub_errno; |
| } |
| else if (! grub_strcmp ("title-color", name)) |
| grub_video_parse_color (value, &view->title_color); |
| else if (! grub_strcmp ("message-color", name)) |
| grub_video_parse_color (value, &view->message_color); |
| else if (! grub_strcmp ("message-bg-color", name)) |
| grub_video_parse_color (value, &view->message_bg_color); |
| else if (! grub_strcmp ("desktop-image", name)) |
| { |
| struct grub_video_bitmap *raw_bitmap; |
| char *path; |
| path = grub_resolve_relative_path (theme_dir, value); |
| if (! path) |
| return grub_errno; |
| if (grub_video_bitmap_load (&raw_bitmap, path) != GRUB_ERR_NONE) |
| { |
| grub_free (path); |
| return grub_errno; |
| } |
| grub_free(path); |
| grub_video_bitmap_destroy (view->raw_desktop_image); |
| view->raw_desktop_image = raw_bitmap; |
| } |
| else if (! grub_strcmp ("desktop-image-scale-method", name)) |
| { |
| if (! value || ! grub_strcmp ("stretch", value)) |
| view->desktop_image_scale_method = |
| GRUB_VIDEO_BITMAP_SELECTION_METHOD_STRETCH; |
| else if (! grub_strcmp ("crop", value)) |
| view->desktop_image_scale_method = |
| GRUB_VIDEO_BITMAP_SELECTION_METHOD_CROP; |
| else if (! grub_strcmp ("padding", value)) |
| view->desktop_image_scale_method = |
| GRUB_VIDEO_BITMAP_SELECTION_METHOD_PADDING; |
| else if (! grub_strcmp ("fitwidth", value)) |
| view->desktop_image_scale_method = |
| GRUB_VIDEO_BITMAP_SELECTION_METHOD_FITWIDTH; |
| else if (! grub_strcmp ("fitheight", value)) |
| view->desktop_image_scale_method = |
| GRUB_VIDEO_BITMAP_SELECTION_METHOD_FITHEIGHT; |
| else |
| return grub_error (GRUB_ERR_BAD_ARGUMENT, |
| "Unsupported scale method: %s", |
| value); |
| } |
| else if (! grub_strcmp ("desktop-image-h-align", name)) |
| { |
| if (! grub_strcmp ("left", value)) |
| view->desktop_image_h_align = GRUB_VIDEO_BITMAP_H_ALIGN_LEFT; |
| else if (! grub_strcmp ("center", value)) |
| view->desktop_image_h_align = GRUB_VIDEO_BITMAP_H_ALIGN_CENTER; |
| else if (! grub_strcmp ("right", value)) |
| view->desktop_image_h_align = GRUB_VIDEO_BITMAP_H_ALIGN_RIGHT; |
| else |
| return grub_error (GRUB_ERR_BAD_ARGUMENT, |
| "Unsupported horizontal align method: %s", |
| value); |
| } |
| else if (! grub_strcmp ("desktop-image-v-align", name)) |
| { |
| if (! grub_strcmp ("top", value)) |
| view->desktop_image_v_align = GRUB_VIDEO_BITMAP_V_ALIGN_TOP; |
| else if (! grub_strcmp ("center", value)) |
| view->desktop_image_v_align = GRUB_VIDEO_BITMAP_V_ALIGN_CENTER; |
| else if (! grub_strcmp ("bottom", value)) |
| view->desktop_image_v_align = GRUB_VIDEO_BITMAP_V_ALIGN_BOTTOM; |
| else |
| return grub_error (GRUB_ERR_BAD_ARGUMENT, |
| "Unsupported vertical align method: %s", |
| value); |
| } |
| else if (! grub_strcmp ("desktop-color", name)) |
| grub_video_parse_color (value, &view->desktop_color); |
| else if (! grub_strcmp ("terminal-box", name)) |
| { |
| grub_err_t err; |
| err = grub_gui_recreate_box (&view->terminal_box, value, theme_dir); |
| if (err != GRUB_ERR_NONE) |
| return err; |
| } |
| else if (! grub_strcmp ("terminal-border", name)) |
| { |
| view->terminal_border = grub_strtoul (value, 0, 10); |
| if (grub_errno) |
| return grub_errno; |
| } |
| else if (! grub_strcmp ("terminal-left", name)) |
| { |
| unsigned int tmp; |
| int err = theme_get_unsigned_int_from_proportional (value, |
| view->screen.width, |
| &tmp); |
| if (err != GRUB_ERR_NONE) |
| return err; |
| view->terminal_rect.x = tmp; |
| } |
| else if (! grub_strcmp ("terminal-top", name)) |
| { |
| unsigned int tmp; |
| int err = theme_get_unsigned_int_from_proportional (value, |
| view->screen.height, |
| &tmp); |
| if (err != GRUB_ERR_NONE) |
| return err; |
| view->terminal_rect.y = tmp; |
| } |
| else if (! grub_strcmp ("terminal-width", name)) |
| { |
| unsigned int tmp; |
| int err = theme_get_unsigned_int_from_proportional (value, |
| view->screen.width, |
| &tmp); |
| if (err != GRUB_ERR_NONE) |
| return err; |
| view->terminal_rect.width = tmp; |
| } |
| else if (! grub_strcmp ("terminal-height", name)) |
| { |
| unsigned int tmp; |
| int err = theme_get_unsigned_int_from_proportional (value, |
| view->screen.height, |
| &tmp); |
| if (err != GRUB_ERR_NONE) |
| return err; |
| view->terminal_rect.height = tmp; |
| } |
| else if (! grub_strcmp ("title-text", name)) |
| { |
| grub_free (view->title_text); |
| view->title_text = grub_strdup (value); |
| if (! view->title_text) |
| return grub_errno; |
| } |
| else |
| { |
| return grub_error (GRUB_ERR_BAD_ARGUMENT, |
| "%s:%d:%d unknown property `%s'", |
| filename, line_num, col_num, name); |
| } |
| return grub_errno; |
| } |
| |
| struct parsebuf |
| { |
| char *buf; |
| int pos; |
| int len; |
| int line_num; |
| int col_num; |
| const char *filename; |
| char *theme_dir; |
| grub_gfxmenu_view_t view; |
| }; |
| |
| static int |
| has_more (struct parsebuf *p) |
| { |
| return p->pos < p->len; |
| } |
| |
| static int |
| read_char (struct parsebuf *p) |
| { |
| if (has_more (p)) |
| { |
| char c; |
| c = p->buf[p->pos++]; |
| if (c == '\n') |
| { |
| p->line_num++; |
| p->col_num = 1; |
| } |
| else |
| { |
| p->col_num++; |
| } |
| return c; |
| } |
| else |
| return -1; |
| } |
| |
| static int |
| peek_char (struct parsebuf *p) |
| { |
| if (has_more (p)) |
| return p->buf[p->pos]; |
| else |
| return -1; |
| } |
| |
| static int |
| is_whitespace (char c) |
| { |
| return (c == ' ' |
| || c == '\t' |
| || c == '\r' |
| || c == '\n' |
| || c == '\f'); |
| } |
| |
| static void |
| skip_whitespace (struct parsebuf *p) |
| { |
| while (has_more (p) && is_whitespace(peek_char (p))) |
| read_char (p); |
| } |
| |
| static void |
| advance_to_next_line (struct parsebuf *p) |
| { |
| int c; |
| |
| /* Eat characters up to the newline. */ |
| do |
| { |
| c = read_char (p); |
| } |
| while (c != -1 && c != '\n'); |
| } |
| |
| static int |
| is_identifier_char (int c) |
| { |
| return (c != -1 |
| && (grub_isalpha(c) |
| || grub_isdigit(c) |
| || c == '_' |
| || c == '-')); |
| } |
| |
| static char * |
| read_identifier (struct parsebuf *p) |
| { |
| /* Index of the first character of the identifier in p->buf. */ |
| int start; |
| /* Next index after the last character of the identifer in p->buf. */ |
| int end; |
| |
| skip_whitespace (p); |
| |
| /* Capture the start of the identifier. */ |
| start = p->pos; |
| |
| /* Scan for the end. */ |
| while (is_identifier_char (peek_char (p))) |
| read_char (p); |
| end = p->pos; |
| |
| if (end - start < 1) |
| return 0; |
| |
| return grub_new_substring (p->buf, start, end); |
| } |
| |
| static char * |
| read_expression (struct parsebuf *p) |
| { |
| int start; |
| int end; |
| |
| skip_whitespace (p); |
| if (peek_char (p) == '"') |
| { |
| /* Read as a quoted string. |
| The quotation marks are not included in the expression value. */ |
| /* Skip opening quotation mark. */ |
| read_char (p); |
| start = p->pos; |
| while (has_more (p) && peek_char (p) != '"') |
| read_char (p); |
| end = p->pos; |
| /* Skip the terminating quotation mark. */ |
| read_char (p); |
| } |
| else if (peek_char (p) == '(') |
| { |
| /* Read as a parenthesized string -- for tuples/coordinates. */ |
| /* The parentheses are included in the expression value. */ |
| int c; |
| |
| start = p->pos; |
| do |
| { |
| c = read_char (p); |
| } |
| while (c != -1 && c != ')'); |
| end = p->pos; |
| } |
| else if (has_more (p)) |
| { |
| /* Read as a single word -- for numeric values or words without |
| whitespace. */ |
| start = p->pos; |
| while (has_more (p) && ! is_whitespace (peek_char (p))) |
| read_char (p); |
| end = p->pos; |
| } |
| else |
| { |
| /* The end of the theme file has been reached. */ |
| grub_error (GRUB_ERR_IO, "%s:%d:%d expression expected in theme file", |
| p->filename, p->line_num, p->col_num); |
| return 0; |
| } |
| |
| return grub_new_substring (p->buf, start, end); |
| } |
| |
| static grub_err_t |
| parse_proportional_spec (const char *value, signed *abs, grub_fixed_signed_t *prop) |
| { |
| signed num; |
| const char *ptr; |
| int sig = 0; |
| *abs = 0; |
| *prop = 0; |
| ptr = value; |
| while (*ptr) |
| { |
| sig = 0; |
| |
| while (*ptr == '-' || *ptr == '+') |
| { |
| if (*ptr == '-') |
| sig = !sig; |
| ptr++; |
| } |
| |
| num = grub_strtoul (ptr, (char **) &ptr, 0); |
| if (grub_errno) |
| return grub_errno; |
| if (sig) |
| num = -num; |
| if (*ptr == '%') |
| { |
| *prop += grub_fixed_fsf_divide (grub_signed_to_fixed (num), 100); |
| ptr++; |
| } |
| else |
| *abs += num; |
| } |
| return GRUB_ERR_NONE; |
| } |
| |
| |
| /* Read a GUI object specification from the theme file. |
| Any components created will be added to the GUI container PARENT. */ |
| static grub_err_t |
| read_object (struct parsebuf *p, grub_gui_container_t parent) |
| { |
| grub_video_rect_t bounds; |
| |
| char *name; |
| name = read_identifier (p); |
| if (! name) |
| goto cleanup; |
| |
| grub_gui_component_t component = 0; |
| if (grub_strcmp (name, "label") == 0) |
| { |
| component = grub_gui_label_new (); |
| } |
| else if (grub_strcmp (name, "image") == 0) |
| { |
| component = grub_gui_image_new (); |
| } |
| else if (grub_strcmp (name, "vbox") == 0) |
| { |
| component = (grub_gui_component_t) grub_gui_vbox_new (); |
| } |
| else if (grub_strcmp (name, "hbox") == 0) |
| { |
| component = (grub_gui_component_t) grub_gui_hbox_new (); |
| } |
| else if (grub_strcmp (name, "canvas") == 0) |
| { |
| component = (grub_gui_component_t) grub_gui_canvas_new (); |
| } |
| else if (grub_strcmp (name, "progress_bar") == 0) |
| { |
| component = grub_gui_progress_bar_new (); |
| } |
| else if (grub_strcmp (name, "circular_progress") == 0) |
| { |
| component = grub_gui_circular_progress_new (); |
| } |
| else if (grub_strcmp (name, "boot_menu") == 0) |
| { |
| component = grub_gui_list_new (); |
| } |
| else |
| { |
| /* Unknown type. */ |
| grub_error (GRUB_ERR_IO, "%s:%d:%d unknown object type `%s'", |
| p->filename, p->line_num, p->col_num, name); |
| goto cleanup; |
| } |
| |
| if (! component) |
| goto cleanup; |
| |
| /* Inform the component about the theme so it can find its resources. */ |
| component->ops->set_property (component, "theme_dir", p->theme_dir); |
| component->ops->set_property (component, "theme_path", p->filename); |
| |
| /* Add the component as a child of PARENT. */ |
| bounds.x = 0; |
| bounds.y = 0; |
| bounds.width = -1; |
| bounds.height = -1; |
| component->ops->set_bounds (component, &bounds); |
| parent->ops->add (parent, component); |
| |
| skip_whitespace (p); |
| if (read_char (p) != '{') |
| { |
| grub_error (GRUB_ERR_IO, |
| "%s:%d:%d expected `{' after object type name `%s'", |
| p->filename, p->line_num, p->col_num, name); |
| goto cleanup; |
| } |
| |
| while (has_more (p)) |
| { |
| skip_whitespace (p); |
| |
| /* Check whether the end has been encountered. */ |
| if (peek_char (p) == '}') |
| { |
| /* Skip the closing brace. */ |
| read_char (p); |
| break; |
| } |
| |
| if (peek_char (p) == '#') |
| { |
| /* Skip comments. */ |
| advance_to_next_line (p); |
| continue; |
| } |
| |
| if (peek_char (p) == '+') |
| { |
| /* Skip the '+'. */ |
| read_char (p); |
| |
| /* Check whether this component is a container. */ |
| if (component->ops->is_instance (component, "container")) |
| { |
| /* Read the sub-object recursively and add it as a child. */ |
| if (read_object (p, (grub_gui_container_t) component) != 0) |
| goto cleanup; |
| /* After reading the sub-object, resume parsing, expecting |
| another property assignment or sub-object definition. */ |
| continue; |
| } |
| else |
| { |
| grub_error (GRUB_ERR_IO, |
| "%s:%d:%d attempted to add object to non-container", |
| p->filename, p->line_num, p->col_num); |
| goto cleanup; |
| } |
| } |
| |
| char *property; |
| property = read_identifier (p); |
| if (! property) |
| { |
| grub_error (GRUB_ERR_IO, "%s:%d:%d identifier expected in theme file", |
| p->filename, p->line_num, p->col_num); |
| goto cleanup; |
| } |
| |
| skip_whitespace (p); |
| if (read_char (p) != '=') |
| { |
| grub_error (GRUB_ERR_IO, |
| "%s:%d:%d expected `=' after property name `%s'", |
| p->filename, p->line_num, p->col_num, property); |
| grub_free (property); |
| goto cleanup; |
| } |
| skip_whitespace (p); |
| |
| char *value; |
| value = read_expression (p); |
| if (! value) |
| { |
| grub_free (property); |
| goto cleanup; |
| } |
| |
| /* Handle the property value. */ |
| if (grub_strcmp (property, "left") == 0) |
| parse_proportional_spec (value, &component->x, &component->xfrac); |
| else if (grub_strcmp (property, "top") == 0) |
| parse_proportional_spec (value, &component->y, &component->yfrac); |
| else if (grub_strcmp (property, "width") == 0) |
| parse_proportional_spec (value, &component->w, &component->wfrac); |
| else if (grub_strcmp (property, "height") == 0) |
| parse_proportional_spec (value, &component->h, &component->hfrac); |
| else |
| /* General property handling. */ |
| component->ops->set_property (component, property, value); |
| |
| grub_free (value); |
| grub_free (property); |
| if (grub_errno != GRUB_ERR_NONE) |
| goto cleanup; |
| } |
| |
| cleanup: |
| grub_free (name); |
| return grub_errno; |
| } |
| |
| static grub_err_t |
| read_property (struct parsebuf *p) |
| { |
| char *name; |
| |
| /* Read the property name. */ |
| name = read_identifier (p); |
| if (! name) |
| { |
| advance_to_next_line (p); |
| return grub_errno; |
| } |
| |
| /* Skip whitespace before separator. */ |
| skip_whitespace (p); |
| |
| /* Read separator. */ |
| if (read_char (p) != ':') |
| { |
| grub_error (GRUB_ERR_IO, |
| "%s:%d:%d missing separator after property name `%s'", |
| p->filename, p->line_num, p->col_num, name); |
| goto done; |
| } |
| |
| /* Skip whitespace after separator. */ |
| skip_whitespace (p); |
| |
| /* Get the value based on its type. */ |
| if (peek_char (p) == '"') |
| { |
| /* String value (e.g., '"My string"'). */ |
| char *value = read_expression (p); |
| if (! value) |
| { |
| grub_error (GRUB_ERR_IO, "%s:%d:%d missing property value", |
| p->filename, p->line_num, p->col_num); |
| goto done; |
| } |
| /* If theme_set_string results in an error, grub_errno will be returned |
| below. */ |
| theme_set_string (p->view, name, value, p->theme_dir, |
| p->filename, p->line_num, p->col_num); |
| grub_free (value); |
| } |
| else |
| { |
| grub_error (GRUB_ERR_IO, |
| "%s:%d:%d property value invalid; " |
| "enclose literal values in quotes (\")", |
| p->filename, p->line_num, p->col_num); |
| goto done; |
| } |
| |
| done: |
| grub_free (name); |
| return grub_errno; |
| } |
| |
| /* Set properties on the view based on settings from the specified |
| theme file. */ |
| grub_err_t |
| grub_gfxmenu_view_load_theme (grub_gfxmenu_view_t view, const char *theme_path) |
| { |
| grub_file_t file; |
| struct parsebuf p; |
| |
| p.view = view; |
| p.theme_dir = grub_get_dirname (theme_path); |
| |
| file = grub_file_open (theme_path, GRUB_FILE_TYPE_THEME); |
| if (! file) |
| { |
| grub_free (p.theme_dir); |
| return grub_errno; |
| } |
| |
| p.len = grub_file_size (file); |
| p.buf = grub_malloc (p.len); |
| p.pos = 0; |
| p.line_num = 1; |
| p.col_num = 1; |
| p.filename = theme_path; |
| if (! p.buf) |
| { |
| grub_file_close (file); |
| grub_free (p.theme_dir); |
| return grub_errno; |
| } |
| if (grub_file_read (file, p.buf, p.len) != p.len) |
| { |
| grub_free (p.buf); |
| grub_file_close (file); |
| grub_free (p.theme_dir); |
| return grub_errno; |
| } |
| |
| if (view->canvas) |
| view->canvas->component.ops->destroy (view->canvas); |
| |
| view->canvas = grub_gui_canvas_new (); |
| if (!view->canvas) |
| goto fail; |
| ((grub_gui_component_t) view->canvas) |
| ->ops->set_bounds ((grub_gui_component_t) view->canvas, |
| &view->screen); |
| |
| while (has_more (&p)) |
| { |
| /* Skip comments (lines beginning with #). */ |
| if (peek_char (&p) == '#') |
| { |
| advance_to_next_line (&p); |
| continue; |
| } |
| |
| /* Find the first non-whitespace character. */ |
| skip_whitespace (&p); |
| |
| /* Handle the content. */ |
| if (peek_char (&p) == '+') |
| { |
| /* Skip the '+'. */ |
| read_char (&p); |
| read_object (&p, view->canvas); |
| } |
| else |
| { |
| read_property (&p); |
| } |
| |
| if (grub_errno != GRUB_ERR_NONE) |
| goto fail; |
| } |
| |
| /* Set the new theme path. */ |
| grub_free (view->theme_path); |
| view->theme_path = grub_strdup (theme_path); |
| goto cleanup; |
| |
| fail: |
| if (view->canvas) |
| { |
| view->canvas->component.ops->destroy (view->canvas); |
| view->canvas = 0; |
| } |
| |
| cleanup: |
| grub_free (p.buf); |
| grub_file_close (file); |
| grub_free (p.theme_dir); |
| return grub_errno; |
| } |